75
C ++ Objective-C\ 2.3 ko Pierre Chatelier e-mail: [email protected] Copyright c 2005, 2006, 2007, 2008, 2009 Pierre Chatelier \ : @[, ü, Korean Translation : BitNa Kim, Young-Min Kang, DongGyu Park l@ L øt ü ¨ : http://pierre.chachatelier.fr/programmation/objective-c.php \ \ l@ L øt ü ¨ : http://ivis.cwnu.ac.kr/tc/dongupak/152 t 8 /·@·\˜ ü ¨ This document is also available in French and English Ce document est aussi disponible en français et en anglais With special thanks to: For their attentive reading and many helpful comments, I would like to thank Pascal Bleuyard, Jérôme Cornet, François Delobel and Jean-Daniel Dupas, whose help was important in making this work the best possible. Jack Nutting, Ben Rimmington and Mattias Arrelid have also provided many feedback. Jonathon Mah has been particularly implied in bringing a lot of very judicious corrections. They are not responsible of any mistake I could add after their reviewing. 1

C++에서 Objective-C까지 번역자료

Embed Size (px)

DESCRIPTION

Pirre Cha

Citation preview

Page 1: C++에서 Objective-C까지 번역자료

C++에서 Objective-C로버전 2.3 ko

Pierre Chateliere-mail: [email protected]

Copyright c© 2005, 2006, 2007, 2008, 2009 Pierre Chatelier

한글 번역 : 김빛나, 강영민, 박동규

Korean Translation : BitNa Kim, Young-Min Kang, DongGyu Park

원본은 다음 웹사이트에서 볼 수 있습니다 :http://pierre.chachatelier.fr/programmation/objective-c.php

최신 한글 번역본은 다음 웹사이트에서 볼 수 있습니다 :http://ivis.cwnu.ac.kr/tc/dongupak/152

이 문서는 프랑스어와 영어로도 볼 수 있습니다

This document is also available in French and EnglishCe document est aussi disponible en français et en anglais

With special thanks to: For their attentive reading and many helpful comments, I would liketo thank Pascal Bleuyard, Jérôme Cornet, François Delobel and Jean-Daniel Dupas, whosehelp was important in making this work the best possible. Jack Nutting, Ben Rimmington andMattias Arrelid have also provided many feedback. Jonathon Mah has been particularly impliedin bringing a lot of very judicious corrections.They are not responsible of any mistake I could add after their reviewing.

1

Page 2: C++에서 Objective-C까지 번역자료

Contents목차 2

개요 5

1 Objective-C와 코코아 61.1 Objective-C의 간단한 역사 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61.2 Objective-C 2.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

2 문법 개요 72.1 키워드 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.2 주석 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.3 코드와 선언의 혼합 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.4 새로운 형식과 값 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

2.4.1 BOOL, YES, NO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.4.2 nil, Nil 과 id . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.4.3 SEL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.4.4 @encode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

2.5 소스 코드의 구성: .h와 .m 파일 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82.6 클래스 이름에서 왜 NS를 사용하는가? . . . . . . . . . . . . . . . . . . . . . . . . . 82.7 함수와 메소드의 차이 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

3 클래스와 객체 103.1 루트 클래스, id 형, nil 과 Nil 값 . . . . . . . . . . . . . . . . . . . . . . . . . . . 103.2 클래스 선언 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

3.2.1 속성과 메소드 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103.2.2 전치 선언 : @class, @protocol . . . . . . . . . . . . . . . . . . . . . . . . . 113.2.3 public, private, protected . . . . . . . . . . . . . . . . . . . . . . . . . . 123.2.4 static 속성 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

3.3 메소드 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133.3.1 프로토타입과 호출, 인스턴스 메소드, 클래스 메소드 . . . . . . . . . . . . . 133.3.2 this, self, 그리고 super . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143.3.3 메소드 안에서 인스턴스 변수 접근하기 . . . . . . . . . . . . . . . . . . . . 153.3.4 프로토타입 식별자와 시그너처, 오버로딩 . . . . . . . . . . . . . . . . . . . 153.3.5 멤버 함수에 대한 포인터: 선택자(Selector) . . . . . . . . . . . . . . . . . . 173.3.6 매개변수(parameter)의 디폴트 값 . . . . . . . . . . . . . . . . . . . . . . . 193.3.7 가변 개수의 인자 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193.3.8 익명 인자 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193.3.9 함수 원형(prototype) 한정어(modifier) - const, static, virtual, “= 0”,

friend, throw . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193.4 메세지와 전송 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

3.4.1 nil에게 메시지 보내기 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203.4.2 알려지지 않은 객체에 메시지 위임 . . . . . . . . . . . . . . . . . . . . . . . 203.4.3 포워딩(forwarding): 알 수 없는 메시지 다루기 . . . . . . . . . . . . . . . . 203.4.4 하향 형변환(downcasting) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

4 상속 224.1 단순 상속 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224.2 다중 상속 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224.3 가상성 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

4.3.1 가상 메소드 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224.3.2 가상 메소드의 암묵적인 재정의 . . . . . . . . . . . . . . . . . . . . . . . . 224.3.3 가상 상속 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

4.4 프로토콜 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234.4.1 공식 프로토콜 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234.4.2 선택적 메소드 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

2

Page 3: C++에서 Objective-C까지 번역자료

4.4.3 비공식 프로토콜 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254.4.4 protocol 타입 객체 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264.4.5 분리된 객체를 위한 메시지 자격심사 . . . . . . . . . . . . . . . . . . . . . 26

4.5 클래스 카테고리 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274.6 프토토콜, 카테고리, 서브클래싱의 통합 사용 . . . . . . . . . . . . . . . . . . . . . 28

5 인스턴스화 295.1 생성자, 초기화 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

5.1.1 할당과 초기화의 차이 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295.1.2 alloc과 init 사용하기 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295.1.3 올바른 초기화의 예 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305.1.4 self = [super init...] . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315.1.5 초기화 실패 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325.1.6 alloc+init으로 생성자를 “쪼개기” . . . . . . . . . . . . . . . . . . . . . . 335.1.7 디폴트 생성자 : 초기화 지정 . . . . . . . . . . . . . . . . . . . . . . . . . . 335.1.8 초기화 리스트와 인스턴스 데이터의 디폴드 값 . . . . . . . . . . . . . . . . 355.1.9 가상 생성자 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355.1.10 클래스 생성자 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

5.2 소멸자 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355.3 복사 연산자 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

5.3.1 고전적인 복제방식, copy, copyWithZone:, NSCopyObject() . . . . . . . . . 365.3.2 NSCopyObject() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375.3.3 더미 복제, 치환성, mutableCopy 와 mutableCopyWithZone: . . . . . . . . 38

6 메모리 관리 406.1 new 와 delete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406.2 참조 횟수 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406.3 alloc, copy, mutableCopy, retain, release . . . . . . . . . . . . . . . . . . . . . 406.4 자동 해제 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

6.4.1 autorelease의 중요성 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416.4.2 오토릴리즈 풀(autorelease pool) . . . . . . . . . . . . . . . . . . . . . . . . 426.4.3 다수의 오토릴리즈 풀 사용하기 . . . . . . . . . . . . . . . . . . . . . . . . 426.4.4 autorelease에서의 주의사항 . . . . . . . . . . . . . . . . . . . . . . . . . . 426.4.5 autorelease와 retain . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 436.4.6 편리한 생성자, 가상 생성자 . . . . . . . . . . . . . . . . . . . . . . . . . . . 436.4.7 설정자 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456.4.8 접근자(Getters) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

6.5 리테인 순환(retain cycles) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 496.6 가비지 수집기 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

6.6.1 finalize . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506.6.2 weak, strong . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506.6.3 NSMakeCollectable() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506.6.4 AutoZone . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

7 예외 처리 51

8 다중 쓰레딩 538.1 쓰레드-세이프티 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 538.2 @synchronized . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53

9 Objective-C 문자열 549.1 Objective-C의 유일한 정적객체 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 549.2 NSString과 인코딩 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 549.3 객체의 설명, %@ 포맷 확장, NSString을 C 문자열로 . . . . . . . . . . . . . . . . . . 54

3

Page 4: C++에서 Objective-C까지 번역자료

10 C++ 고유의 특징 5510.1 참조 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5510.2 인라인 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5510.3 템플릿 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5510.4 연산자 오버로딩 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5510.5 프렌드 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5510.6 const 메소드 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5510.7 생성자에서 초기화 리스트 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

11 STL과 코코아 5611.1 컨테이너 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5611.2 반복자(Iterator) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

11.2.1 고전적인 열거 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5611.2.2 빠른 나열형 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

11.3 함수자 (함수 객체) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5711.3.1 셀렉터 사용하기 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5711.3.2 IMP 캐싱 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

11.4 알고리즘 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

12 Implicit code 5812.1 Key-value 코딩 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

12.1.1 원리 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5812.1.2 차단 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5912.1.3 프로토타입 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6012.1.4 고급 기능 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60

12.2 프로퍼티 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6012.2.1 프로퍼티의 활용 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6012.2.2 프로퍼티의 서술 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6012.2.3 프로퍼티 속성 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6212.2.4 프로퍼티의 사용자 지정 구현 . . . . . . . . . . . . . . . . . . . . . . . . . . 6312.2.5 프로퍼티에 접근하는 문법 . . . . . . . . . . . . . . . . . . . . . . . . . . . 6312.2.6 고급 내용 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64

13 동적성질 6513.1 RTTI (런타임 형 정보) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65

13.1.1 class, superclass, isMemberOfClass, isKindOfClass . . . . . . . . . . . 6513.1.2 conformsToProtocol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6513.1.3 respondsToSelector, instancesRespondToSelector . . . . . . . . . . . . 6513.1.4 id형을 가진 강한 타이핑과 약한 타이핑 . . . . . . . . . . . . . . . . . . . . 66

13.2 런타임시 Objective-C 클래스의 조작 . . . . . . . . . . . . . . . . . . . . . . . . . . 66

14 Objective-C++ 67

15 Objective-C의 미래 6715.1 블록(block) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

15.1.1 지원과 사용사례 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6715.1.2 구문 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6815.1.3 환경의 캡쳐링 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6815.1.4 __block 변수 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68

결론 70

참고문헌 71

문서 개정내역 72

색인 73

4

Page 5: C++에서 Objective-C까지 번역자료

소개

이 문서는 C++와 Objective-C 사이의 다리 역할을 하려는 의도로 작성되었습니다. 지금도많은 교재와 문서들이 Objective-C로 객체지향 모형을 가르치고 있지만, 고급 C++ 개발자들이

이미알고있는내용과 Objective-C의개념을비교하여이해를도와주는문서는없습니다.이러한문제 때문에, 맥과 아이폰 프로그램을 처음 시작하는 사람들에게는 Objective-C 언어가 코코아프로그래밍을도와주기는커녕오히려개발을어렵게만드는장애물처럼보입니다: (참조. 6쪽 1).실제로 Objective-C가가진객체지향적특성이기존의 C++나 Java와같은언어들과는차이가매우큰데, 이런 이유가 코코아 프로그램을 익히는 데에 큰 진입장벽이 됩니다. 이런 이유로 저도 코코아 API에서 제공하는 여러 유용한 개념들을 익히는데에 시간이 다소 걸렸습니다. 저는 개발자가언어를이해하지못해 Objective-C를포기하거나그기능을잘못사용하는일이없기를바랍니다.이 문서는 완벽한 레퍼런스는 아니지만 빠른 길잡이 역할은 할 것입니다. 객체지향과 세부적인문법개념에관해서더욱자세한설명을원한다면전문적인 Objective-C 지침서를읽어보는것이좋습니다[4].

C#과 Objective-C를 비교하는 별도의 문서도 필요할지도 모르지만, 객체지향성이 약한 C++

보다는 C#이 훨씬 더 Objective-C와 가깝기 때문에 C# 개발자는 분명히 Objective-C를 더 빨리배울수있을것입니다.제가보기엔, C#은 C++보다나은고급개념을가지고있음에도불구하고

그다지 흥미롭지는 않는데, 이는 Objective-C가 단순하면서도 강력한 특성을 가지는데 비해 C#은 복잡한 접근법을 제공하기 때문입니다. 그리고 .NET보다는 코코아(Cocoa) API의 품질이 훨씬낫기 때문이기도 합니다. 그러나 이러한 개인적인 의견이 본 문서의 주제는 아닙니다.

5

Page 6: C++에서 Objective-C까지 번역자료

1 Objective-C와 코코아먼저 Objective-C와코코아(Cocoa)를잘구별해야한다. Objective-C는프로그래밍언어이고,코코아(Cocoa)는 MacOS X(“맥 오에스 텐”으로 읽는다)의 기본적인 프로그래밍 환경을 제공하는 클래스 집합이다. 이론적으로는 코코아 없이도 Objective-C를 사용할 수 있다. Objective-C를처리할 수 있도록 하는 gcc 처리기(front-end)도 존재한다. 그러나 MacOS X 에서 이 둘은 뗄 수없는 관계로, Objective-C가 제공하는 대부분의 클래스가 코코아의 일부이다.정확히는 Apple사가 OpenStep 표준을 MacOS X용으로 구현하여 1994년에 발표한 것이 코코

아인데, Objective-C로 만든 개발자 프레임워크(framework)로 구성되어 있다. OpenStep 표준을구현한 다른 예로는 GNUstep 프로젝트 [6]가 있는데, 무료이다. 이 프로젝트는 대부분의 유닉스시스템에서 이식이 가능해지는 것을 목표로 개발중인 상태이다.

1.1 Objective-C의 간단한 역사프로젝트 착수 단계에서부터 개선, 표준화 및 공식 발표 사이에 시차가 있어, Objective-C 가탄생한 정확한 날짜를 말하기는 어렵다. 그러나, Objective-C의 조상언어(C와 Smalltalk언어)와“도전자” 사이의 대략적인 역사는 그림 1에서 볼 수 있다.

1972 1978 19891980 1995 1998-991983

Smalltak-72 Smalltak-80

C ANSI C C 99

C++ Standard C++ C++0x draft

Objective-C Objective-C 2.0

Objective-C++

Java

C♯

2001

C♯2 (C♯3 to come)

2005-07

Figure 1: Java, C, C#, C++, Objective-C의 연대표

Smalltalk-80은최초의 “진짜”객체지향언어이다.또한 C++와 Objective-C는 C언어의기능을포함하여발전된상위집합(superset)을만든두갈래의서로다른방식이다. C++가매우정적이며,실행성능을높이는것을목표로하고있다면 Objective-C는구문특성이나동적성질이 SmallTalk에 매우 가깝다. Java는 C++ 사용자를 대상으로 하지만, 그 객체모델은 Smalltalk에서 영감을 얻었다. 그래서 제목과 달리 이 문서는 많은 면에서 Java를 참조하였다. 마이크로소프트가 개발한C# 언어는 Objective-C의 직접적인 도전자라고 할만 하다.

Objective-C++는 Objective-C와 C++를 합한 것이라 할 수 있다. 현재 사용이 가능한 상태이지만, 몇 가지 기능들이 아직 완벽하지 않다. Objective-C++의 목표는 Objective-C와 C++의 문법을

혼용할수있도록하여이둘의장점을최대로살려보려는의도록개발되었다 (비고. 67쪽 14절).

1.2 Objective-C 2.0이 문서는 MacOS X 10.5와 나란히 발표된 Objective-C 2.0의 새로운 기능을 고려하여 갱신되었다. 이 새로운 기능들은 깊이 있는 내용의 기술적 개선인데, 개발자들이 느낄 수 있는 고수준관점에서의 수정은 다음과 같은 것들이다.

• 가비지 수집기 : 비고. 49쪽 6.6절

• 프로퍼티 : 비고. 60쪽 12.2절

• 빠른 열거형 : 비고. 57쪽 11.2.2절

• 프로토콜을 위한 @optional이나 @required와 같은 새로운 예약어 : 비고. 23쪽 4.4절

• 실시간 Objective-C 라이브러리 특성의 개선 : 비고. 66쪽 13.2절

앞으로 나올 절에서 이러한 특징들 하나 하나를 세부적으로 다룰 것이다.

6

Page 7: C++에서 Objective-C까지 번역자료

2 문법 개요

2.1 키워드

Objective-C는 C 언어를 포함한다. C++와 마찬가지로 C에서 허용하는 나쁜 기능들을 일부러쓰지 않고 잘 작성된 C 프로그램이라면 Objective-C로 컴파일할 수 있다. Objective-C에는 몇가지 개념과 관련된 키워드만 추가되었다. 기존 코드와의 충돌을 피하기 위해, 이러한 키워드는 @ 문자로 시작한다. 추가된 키워드는 다음과 같다: @class, @interface, @implementation,@public, @private, @protected, @try, @catch, @throw, @finally, @end, @protocol, @selector,@synchronized, @encode, @defs (애플사의 개발 문서 [4]에는 더이상 기록된 것이 없음).

Objective-C 2.0은(비고. 6쪽 1.2절), @optional, @required, @property, @dynamic, @synthesize를 키워드로 추가했고, nil, Nil 값과 id, SEL, BOOL 형(type)과 YES와 NO의 부울형 값을 사용할수 있다. 마지막으로, 다음과 같은 몇 가지 키워드는 특별한 경우의 맥락 내에서만 사용되고, 그밖에서는예약어가아니다: in, out, inout, bycopy, byref, oneway (이것들은프로토콜정의할때볼 수 있다. 비고. 26쪽 4.4.5절) getter, setter, readwrite, readonly, assign, retain, copy,nonatomic (이것들은 프로퍼티를 정의할 때 볼 수 있다. 비고. 60쪽 12.2절).언어 자체의 키워드와 루트 클래스(root class, 비고. 10쪽 3.1절)인 NSObject(모든 클래스

의 부모)를 통해 상속받은 몇몇 메소드는 헷갈릴 수 있다. 예를 들어, alloc, retain, release,autorelease라는 메모리 관리기능을 가지는 “키워드”처럼 보이는 명령은 사실 NSObject의 메소드들이다. super와 self (비고. 13쪽 3.3.1절) 는 키워드로 이해될 수도 있지만, 사실 self는각 메소드에 넘겨지는 숨겨진 매개 변수이며, super는 self를 다른 방식으로 쓰도록 컴파일러에게 요구하는 명령이다. 어찌 되었든, 거짓 키워드와 진짜 키워드 사이의 혼동이 보통의 프로그램개발에서 큰 문제가 되지는 않는다.

2.2 주석

Objective-C에는 /* . . . */와 //의 두 가지 주석을 모두 사용할 수 있다.

2.3 코드와 선언의 혼합

C++에서처럼, 명령 블록 중간에 변수 선언문을 삽입할 수 있다.

2.4 새로운 형식과 값

2.4.1 BOOL, YES, NO

C++에서 boolean 타입은 bool인데, Objective-C에서는 BOOL 이고, YES나 NO로 설정할 수 있다.

2.4.2 nil, Nil 과 id

이 세 개의 키워드는 이후에 설명할 것인데, 간단히 언급하면 다음과 같다.

• Objective-C에는모든객체에대한포인터타입으로 NSObject *와같은역할을하는 id형이있다. 이것은 약한 형검사(weak-typing) 도구이다.

• nil은 객체에 대한 포인터로 NULL 과 같다. 그러나 nil과 NULL은 서로 호환되지 않는다.

• Nil은 클래스 포인터에 대한 nil이다. Objective-C에서는 클래스도 객체이다. 즉, 클래스는메타 클래스(meta-class)의 인스턴스이다.1

2.4.3 SEL

SEL 타입은 어떤 클래스 인스턴스 객체와도 무관한 메소드 식별자를 뜻하는 실렉터(selector)를저장할 수 있디. 이 값은 @selector에 대한 호출을 통해서 얻을 수 있다. 실렉터는 기술적으로는 실제 함수 포인터가 아니지만, C언어에서 사용하는 함수(C++의 메소드)에 대한 포인터의일종으로 생각할 수 있다. 자세한 내용은 17쪽 3.3.5절을 참조하라.

1이와 달리 C++의 클래스는 객체를 만드는 설계도로 설명할 수 있다. [譯註]

7

Page 8: C++에서 Objective-C까지 번역자료

2.4.4 @encode

자료형들 사이의 상호운용성(interoperability)를 위하여 Objective-C의 모든 자료형은 그것이 사용자정의형이나함수프로토타입이라할지라도 [4]에나타나있는형식에따라 ASCII로인코딩할수 있다. @encode(a type )를 호출하면 주어진 자료형을 표현하는 C 문자열(char*)이 반환된다.

2.5 소스 코드의 구성: .h와 .m 파일

C++과 마찬가지로, 각 클래스의 인터페이스와 구현부에 해당하는 코드는 서로 분리하는 것이좋다. Objective-C는 헤더파일을 위하여 .h를, 코드가 들어가는 파일로 .m 파일을 사용한다. 그리고, .mm 파일은 Objective-C++를 위해 사용한다(67쪽 14절을 볼 것). Objective-C는 #include를 대체하는 #import 지시어(directive)를 사용한다. 모든 C 헤더 파일은 중복 포함을 방지하기위하여 컴파일 보호 장치를 사용해야 한다2. 이러한 불편은 Objective-C의 #import를 사용하면자동적으로 해결된다. 다음은 전형적인 헤더와 구현파일의 예인데, Objective-C의 구문은 나중에자세히 설명하겠다.

C++

// Foo.h 파일

#ifndef __FOO_H__ //이중 포함을 방지

#define __FOO_H__ //

class Foo{...};

#endif

// Foo.cpp 파일

#include "Foo.h"

...

Objective-C//Foo.h 파일

//클래스 선언은 Java 키워드인//"interface"와 의미가

//다르니 혼동하지 말것

@interface Foo : NSObject{...}@end

// Foo.m 구현 파일

#import "Foo.h"

@implementation Foo...@end

2.6 클래스 이름에서 왜 NS를 사용하는가?이 문서에 나타나는 거의 모든 클래스 이름은 NSObject 또는 NSString처럼 NS로 시작한다. 이유는 간단하다: 이 클래스들은 모두 코코아 클래스이고, 대부분의 코코아 클래스는 NeXTStep환경에서 처음 만들어졌기 때문에 NS로 시작한다.프로그램 작성시 클래스가 어디서 온 것인지를 식별하기 위해 접두어를 사용하는 것이 일반

적인 관행이다3.

2.7 함수와 메소드의 차이

Objective-C는 단순히 “대괄호(꺽쇠)를 사용하여 함수를 호출”하는 언어가 아니다. 이런 생각은아래의 코드

2#ifndef ... 와 같은 매크로를 사용한다 [譯註]3예를 들어 게임개발에 널리 사용되는 cocos2d 클래스들은 CC를 접두어로 사용한다. 그러나 C++와 C#의 경우는

네임스페이스를 통하여 클래스 이름이 충돌나는 것을 방지한다 [譯註]

8

Page 9: C++에서 Objective-C까지 번역자료

object.doSomething();

대신에

[object doSomething];

의형태로작성된코드를보고있으면쉽게떠올릴수있는생각이다.그러나사실 Objective-C는 C의 상위집합이기 때문에 함수를 선언하고 구현하며 호출하는 문법은 C와 완전히 동일하다. 반면,C에서 존재하지 않는 메소드는 대괄호([ ])를 사용하는 특별한 구문법을 가지고 있다. 그런데,이러한차이는문법적으로다른것에그치는것이아니라,의미에있어서도차이를갖는다.자세한내용은 10쪽 3.2절에서 더 깊이 다룰 것이다: 이것은 메소드의 호출이 아니라 메시지를 보내는것이다. 이것은 그냥 단순히 학술적인 구분이 아니라 Objective-C의 메커니즘이 어떠한지를 드러내는 특징이다. 소스 코드 작성에 있어서는 별 차이가 없지만, 이 메커니즘은 더욱 동적인 특성을갖는다. 예를 들면, Objective-C는 실행중에 메소드를 추가하는 일도 가능하다 (비고. 66쪽 13.2절). 이런 구문은 읽기도 쉬운데, 특히 중첩된 호출문의 경우에 더욱 좋다 (비고. 13쪽 3.3.1절).

9

Page 10: C++에서 Objective-C까지 번역자료

3 클래스와 객체

Objective-C는객체지향언어로클래스와객체를다룬다.이상적인객체모델과는상당한차이를보이는 C++과달리, Objective-C는엄격한객체모델을사용한다. 4 예를들면, Objective-C에서는클래스도 역시 객체이며 동적으로 관리할 수 있다. 즉, 실행 시간에 새로운 클래스를 추가하고,이 클래스의 이름으로 인스턴스를 만들거나, 클래스에게 어떤 메소드를 가지고 있는지 물어보는등의 다양한 일들이 가능하다. 이것은 기본적으로 매우 “정적인” 특성을 가진 C++에 추가적으

로 도입된 것에 불과한 RTTI5(비고. 65쪽 13.1절)보다 훨씬 더 강력하다. 게다가 RTTI의 결과가컴파일러에의존적이며,이식성도떨어지기때문에 RTTI사용을권장하지않는것이일반적이다.

3.1 루트 클래스, id 형, nil 과 Nil 값

객체지향언어에서는,개별프로그램이다수의클래스의모음을사용한다. C++와는달리, Objective-C는 루트(root) 클래스라는 것을 정의한다. 새로이 만들어지는 모든 클래스는 이 루트 클래스의자손이어야 한다. 코코아에서 루트 클래스의 이름은 NSObject인데, 이 클래스는 런타임 시스템(run-time system)6을 지원하기 위한 방대한 양의 도구들을 제공한다. 루트 클래스 개념은 특별히Objective-C에서만사용되는용어가아니다.이것은객체모델과관련된것이다. C++는이개념을

사용하지 않지만, Smalltalk과 Java는 사용한다.엄밀히말하면,모든객체는 NSObject타입이어야하고,모든객체에대한포인터는 NSObject*

로 선언될 수 있다. 사실은 객체에 대한 포인터인 NSObject*를 대신해 간단히 id 형을 사용할 수있다. 이것은 임의의 객체를 가리킬 수 있는 (범용) 포인터를 선언하는 빠르고 간편한 방법이며, 정적 형검사 대신 동적인 형검사를 제공한다. 이것은 범용 메소드에서 느슨하게 형 검사를적용하는 데에 매우 유용하다. 7 객체에 대한 null 포인터가 NULL이 아니라 nil로 설정되어야함에도 유의하여야 한다. 이 두 값은 상호 교환되지 않는다. 보통 C 포인터는 NULL로 설정되지만 Objective-C에서 객체에 대한 포인터를 위하여 nil을 새롭게 도입하였다. Objective-C에서클래스는 객체(메타 클래스 인스턴스)이기도 하기때문에 클래스에 대한 포인터를 선언하는 것이가능한다. 그 null 값은 Nil이다.

3.2 클래스 선언

클래스 선언과 구현에 대한 Objective-C와 C++의 모든 차이를 딱 하나의 예로 보이기는 어렵다.구문법만으로는개념적차이를알수없어설명이필요하다.지금부터그차이를차례차례하나씩보이겠다.

3.2.1 속성과 메소드

Objective-C에서 속성은 인스턴스 데이터(instance data)라 불리며, 멤버 함수는 메소드(method)라 부른다.

4C++는 friend 함수를 사용하여 객체 내부의 멤버에 손쉽게 접근하는 편의성을 제공하기도 하며, C 언어와의 호환성을 유지하기 위하여 함수나 메소드를 문법적으로 동일한 방식으로 호출하여 C 개발자들이 쉽게 C++ 개발자가 될

수 있도록 하였다. 이러한 방식에 장점도 분명 있겠지만 객체지향적 개념을 훼손하는 C 언어의 특성을 배제하지 못하여다양한 문제를 일으키기도 한다. 또한 C++는 간략한 원칙보다는 다양한 상황에 대처하는 ad hoc한 문법들이 복잡하게포함되어 학습이나 활용이 매우 어려운 언어이다. [譯註]

5Run-Time Type Information(런타임 형 정보)의 약자 [譯註]6런타임 시스템은 어떤 컴퓨터 언어로 작성된 프로그램이 실행되는 것을 지원하기 위해 설계된 소프트웨어를 뜻한다.

런타임 시스템은 기본적인 저수준 명령어의 구현을 담고 있으며, 고수준 명령어들을 구현하고 형 검사(type checking),디버깅, 코드 생성, 최적화 등의 기능을 지원할 수도 있다. [譯註]

7약한 형 결합 방식은 프로그래밍 언어에서 강한 형 결합(strong typing)의 반대 개념으로 사용되었는데, 데이터의 형변환을 컴파일러나 인터프리터가 내부적으로 수행하는 방식이다. 이 방식은 컴파일 타임에 에러를 검출하지 않기 때문에프로그램에는 좀 더 많은 주의가 필요하지만 객체의 동적인 생성과 사용에 매우 편리한 환경을 제공한다. [譯註]

10

Page 11: C++에서 Objective-C까지 번역자료

C++ Objective-C

class Foo{double x;

public:int f(int x);float g(int x, int y);

};

int Foo::f(int x) {...}float Foo::g(int x, int y) {...}

@interface Foo : NSObject{double x;

}

-(int) f:(int)x;-(float) g:(int)x :(int)y;@end

@implementation Foo-(int) f:(int)x {...}-(float) g:(int)x :(int)y {...}

@end

C++에서는 속성과 메소드가 클래스의 중괄호(brace, ‘{’와 ‘}’) 안에 함께 선언된다. 메소드 구현문법은 영역 결정 연산자(scope resolution operator, Foo:: )가 추가된다는 것 말고는 C의 함수와유사하다.

Objective-C에서는 속성과 메소드를 중괄호내에 함께 섞어 놓을 수 없다. 속성은 중괄호 안에선언되며 메소드는 괄호 뒤에 나타난다. 메소드의 구현은 @implementation 블록에 자리잡는다.이것은 C++와의 중요한 차이점인데, 일부 메소드를 인터페이스에 노출하지 않고 구현할 수

있다는 점이다. 이것은 나중에 자세히 설명될 것이다. 간단히 이야기하자면, 불필요한 선언을제거하여 헤더 파일을 깔끔하게 정리할 수 있다. (불필요한 선언이라 함은 “private” 메소드를선언하거나 소멸자와 같이 드러나지 않게 재정의된 가상메소드 등은 굳이 인터페이스에 선언할

필요가 없다는 뜻이다). 자세한 설명은 22쪽 4.3.2절을 참조하기 바란다.인스턴스 메소드의 앞에는 마이너스 기호(“-”)가 붙고, 클래스 메소드 앞에는 플러스 기호

(“+”)(비고. 19쪽 3.3.9절)가붙는다.이기호는 UML표기법과비슷하기는하지만이것과는전혀상관이 없으며 public이나 private을 구분하는 의미를 가지고 있지 않다. 8 매개변수의 형(type)은 괄호로 묶여지며 각각의 파라미터들은 “:”기호로 분리된다. 프로토타입의 문법에 대한 자세한설명은 다음 페이지의 13쪽 3.3.1절을 참조하라.

Objective-C에서는클래스선언의끝에세미콜론이필요없다.또한클래스를선언하는키워드로 @class가 아니고 @interface를 쓴다는 것에 유의하라. 키워드 @class는 전치(forward) 선언에서만 사용된다(비고. 11쪽). 마지막으로, 클래스에 인스턴스 데이터가 없는 경우의 빈 중괄호는생략할 수 있다.

3.2.2 전치 선언 : @class, @protocol

헤더파일들사이에순환적인의존성이나타나는것을막기위해, C언어는전치(forward)선언을지원한다. 이 전치 선언은 특정한 클래스의 내구 구조는 알 필요가 없지만, 그러한 클래스가 존재한다는 정보는 필요할 경우 프로그램 작성자(코더)가 해당 클래스를 선언할 수 있도록 해준다.전치선언을 위하여 C++에서는 키워드로 class를 사용하는데, Objective-C에서는 @class를 사용한다. 프로토콜의 선언이 있을 것이라는 것을 예측할 수 있도록 키워드 @protocol을 사용하는것도 가능하다(비고. 23쪽 4.4절).

8이 기호들은 다만 비정적(nonstatic) 또는 정적(static) 메소드임을 의미할 뿐이다. 모든 정적 메소드는 객체를 인스턴스화 시키지 않아도 사용할 수 있기 때문에 alloc처럼 클래스가 객체를 생성하기 위한 메소드나 싱글턴(singleton)객체의 메소드로 적합하다. [譯註]

11

Page 12: C++에서 Objective-C까지 번역자료

C++

// Foo.h 파일

#ifndef __FOO_H__#define __FOO_H__

class Bar; //전치 선언

class Foo{Bar* bar;

public:void useBar(void);

};

#endif

// Foo.cpp 파일

#include "Foo.h"#include "Bar.h"

void Foo::useBar(void){...

}

Objective-C// Foo.h 파일

@class Bar; // 전치 선언

@interface Foo : NSObject{Bar* bar;

}

-(void) useBar;

@end

// Foo.m 파일

#import "Foo.h"#import "Bar.h"

@implementation Foo

-(void) useBar{...

}

@end

3.2.3 public, private, protected

객체 지향 모델의 주요한 특징 중에 하나로 데이터 캡슐화(encapsulation)이 있다. 이것은 코드의어떤 부분에서는 해당 데이터가 보이지 않도록 하는 것인데, 이렇게 함으로써 데이터가 훼손되는것을 막는다.9

9이러한 캡슐화를 통해 객체 내부의 데이터를 안전하게 보호할 수 있으며, 이렇게 보호된 데이터가 훼손되지 않은값을 유지하고 있는 상태를 데이터의 ‘완결성(integrity)’라고 한다. 코드가 특정 데이터를 접근할 수 있는지 그렇지 아니한지를 그 데이터를 ‘볼 수 있다’ 혹은 ‘볼 수 없다’로 표현한다. 이러한 데이터 접근성을 ‘가시성(visibility)’라고도부른다. 데이터의 완결성을 유지할 때, 오류도 쉽게 찾을 수 있다. [譯註]

12

Page 13: C++에서 Objective-C까지 번역자료

C++ Objective-C

class Foo{public:int x;int apple();

protected:int y;int pear();

private:int z;int banana();

};

@interface Foo : NSObject{@publicint x;

@protected:int y;

@private:int z;

}

-(int) apple;-(int) pear;-(int) banana;@end

C++에서 속성과 메소드는 public, protected, private 범위 중 하나에 속할 수 있다. 이때 아무런 지정이 없다면 기본적으로 private(비공개) 모드이다.

Objective-C에서는 인스턴스 데이터에만 public, protected, private 모드를 지정할 수 있는데, 아무런 지정을 하지 않으면 protected이다. 메소드는 모두 public 모드이다.10 하지만@interface부분에서선언하지않았던메서드를 @implementation부분에서구현하거나,클래스카테고리 기호를 사용함으로써 private 모드를 흉내낼 수도 있다(비고. 27쪽 4.5절). 이러한 방식이 구현된 메서드를 외부에서 호출하는 것을 완전히 막을 수는 없지만, 메소드의 노출을 줄일수는 있게 된다. 선언을 하지 않고도 메소드를 구현하는 방식은 Objective-C의 특별한 특성인데,22쪽 4.3.2절에 설명되는 특별한 목적을 가지고있다.상속은 public, protected, private으로 구분하여 할 수 없으며(C++에서는 가능하다), 오직

public 방식의 상속만 가능하다. Objective-C의 상속은 C++보다 Java에 가까워 보인다(22쪽 4절).

3.2.4 static 속성

Objective-C에서는 (C++의 static과 같은) 클래스 데이터 속성을 선언할 수가 없다. 그러나 다른방식으로 동일한 일을 할 수는 있다: 그 방법은 구현 파일에서 전역 변수를 사용하는 것이다(이때 스코프(scope)를 제한하도록 static이라는 C 키워드를 사용할 수도 있다). 클래스는 (클래스메소드나 일반적인 메소드를 통해) 이 속성에 대한 접근자를 사용할 수 있으며, 이들 값에 대한초기화는 클래스의 initialize 메소드에서 할 수 있다(비고. 35쪽 5.1.10절).

3.3 메소드

Objective-C에서 메소드에 대한 구문법(syntax)은 일반적인 C 함수를 사용하는 구문법과 다르다. 이 절은 이러한 구문법을 설명하고 그 배경이 되는 메시지 전달 원리에 대해 몇 가지 정보를제공하려고 한다.

3.3.1 프로토타입과 호출, 인스턴스 메소드, 클래스 메소드

• 인스턴스 메소드(일반적인 경우에 해당한다)인 경우 “−”가 앞에 붙고, 클래스 메소드((C++

에서는 static으로 구현함)인 경우에는 “+”가 앞에 붙는다. 이 기호는 public, private를구분하기 위한 UML 표기법과는 전혀 관계가 없다. Objective-C에서 메소드는 항상 public이다.

• 반환(return) 값이나 매개변수의 자료형(type)은 괄호로 묶어서 표시한다.10C++는 메소드나 인스턴스 데이터 모두 public, protected, private가 될 수 있다. [譯註]

13

Page 14: C++에서 Objective-C까지 번역자료

• 매개변수(parameter)는 콜론 기호“:” 로 구분된다.

• 매개변수는 “:”앞에표시되어있는이름인레이블과결합될수있다.이렇게 되면 레이블은메소드 이름의 일부분으로서 메소드 이름을 수정하게 된다. 이런 함수를 호출하는 경우는코드를 읽기가 훨씬 쉬워진다. 사실, 레이블 사용은 체계적이어야 한다. 첫 번째 매개변수는레이블을가질수없는데,메소드이름이바로이첫번째파라미터의레이블이라할수있다.

• 메소드이름을속성이름과동일하게사용해도충돌이생기지않는다.이것은접근자(getter)메소드를 만들때 아주 유용하다(비고. 47쪽 6.4.8절).

C++

//프로토타입void Array::insertObject(void *anObject, unsigned int atIndex)

// "book" 객체를 "shelf" 배열에 사용한다

shelf.insertObject(book, 2);

Objective-C레이블 없음 (C++문장의 직역에 해당)

//프로토타입//"insertObject::"라는 메소드이름에서 콜론은 구분하는 역할

//C++의 스코프 연산자가 아니라는 점에 주의할 것

-(void) insertObject:(id)anObject:(unsigned int)index

// "book" 객체를 "shelf" 배열에 넣는다(레이블 없음).[shelf insertObject:book:2];

레이블을 가진 Objective-C 스타일 표현//프로토타입. "index" 매개변수는 "atIndex"로 레이블 됨

//이 메소드는 이제 "insertObject:atIndex:"로 명명됨

//이 방식의 호출은 문장처럼 쉽게 읽을 수 있다.-(void) insertObject:(id)anObject atIndex:(unsigned int)index

// "book" 객체를 "shelf" 배열에 넣는다(레이블 있음)[shelf insertObject:book:2]; //에러 ![shelf insertObject:book atIndex:2]; //성공

대괄호 구문은 “shelf" 객체의 insertObject ‘메소드를 호출한다’가 아니라, "shelf" 객체에게insertObject라는 ‘메세지를 보낸다’로 읽어야 한다는 것에 유의하라. 이것이 Objective-C의 핵심적 특징이다. 우리는 모든 종류의 객체에게 아무런 메시지라도 보낼 수가 있다. 이때 객체가메세지를처리할수없다면이를무시할수있다(예외가발생하지만프로그램이중단되지는않는다). 메세지가 전달된 시점에 이를 수신한 객체가 이 메시지를 처리할 수 있다면, 수신 메시지와일치하는 메소드가 호출될 것이다. 어떤 클래스가 메세지와 일치하는 메소드를 가지지 않았다는것을 안다면 컴파일러가 경고를 낼 수 있다. 이러한 경우에도 오류(error)로 간주되지 않는 것은포워딩(forwarding)이라는 기능 덕분이다(비고. 20쪽 3.4.3절). 만일 메시지를 받는 대상이 id형으로만알려진경우,컴파일하는도중에경고가발생하지는않겠지만,실행시간에오류가발생할수 있다.

3.3.2 this, self, 그리고 super

Objective-C에는 메시지를 전달할 특별한 대상으로 self와 super가 존재한다. self는 현재 객체(C++에서의 this처럼)이며, super는 부모 클래스이다. this 키워드는 Objective-C에서 존재하지않으며, self로 대치되었다.주의 : 사실 self는 키워드가 아니라, 모든 메소드에게 전달되는 숨겨진 매개변수(parameter)

인데,그값이현재객체이다. self는 C++의 this키워드와는달리그값을바꿀수도있다.하지만이런 변경은 생성자에서만 쓸모가 있다(비고. 29쪽 5.1절).

14

Page 15: C++에서 Objective-C까지 번역자료

3.3.3 메소드 안에서 인스턴스 변수 접근하기

C++에서와 같이, Objective-C 메소드는 현재 객체의 인스턴스 변수를 액세스할 수 있다. C++에서

필요에 따라 사용할 수 있는 this->는 Objective-C에서는 self->로 쓴다.

C++ Objective-C

class Foo{int x;int y;void f(void);

};

void Foo::f(void){x = 1;int y; //this->y와 구분이 모호함

y = 2; //지역변수 y를 사용

this->y = 3; //현재 객체의 y속성임}

@interface Foo : NSObject{int x;int y;

}-(void) f;@end

@implementation Foo-(void) f{x = 1;int y; //this->y와 구분이 모호함

y = 2; //지역변수 y를 사용

self->y = 3; //현재 객체의 y속성임}@end

3.3.4 프로토타입 식별자와 시그너처, 오버로딩

함수란 함수 포인터나 함수 기호로 사용하기 위해 참조할 수 있는 코드 조각이다. 더욱이 함수를식별하는 기호로 함수의 이름이 좋은 후보가 될 수 있지만, 오버로딩(overloading)을 사용할 경우에는 주의가 필요하다. C++과 Objective-C는 프로토타입을 구별하기 위한 서로 상반된 방식을사용한다. C++는매개변수의자료형에근거한방식을사용하고, Objective-C는매개변수레이블에근거한 방식을 사용한다.

C++에서는 매개변수의 자료형이 다르다면 서로 다른 두 함수가 동일한 이름을 가질 수도 있다.메소드를 사용하는 경우에는 const 옵션으로도 메소드를 구별할 수 있다.

C++

int f(int);int f(float); //가능, float는 int와 다르기 때문

class Foo{public:int g(int);int g(float); //가능, float는 int와 다르기 때문

int g(float) const; //가능, const로 구분함

};

class Bar{public:int g(int); //가능, g(int)는 Foo::와 다른 Bar::에 있다

}

15

Page 16: C++에서 Objective-C까지 번역자료

Objective-C에서는 모든함수가 C함수이기때문에오버로드될수없다.오버로딩이가능하려면 gcc에서처럼컴파일러에게 C9911표준을사용하라고지정할수있어야한다.그러나,메소드는C 함수와 다른 문법을 사용하고 있으며, 파라미터 레이블을 이용하여 구별할 수 있다.

Objective-Cint f(int);int f(float); // 에러 : C 함수는 오버로딩할 수 없다

@interface Foo : NSObject{}

-(int) g:(int) x;-(int) g:(float) x; //에러: 이 메소드는 위의 메소드와 동일

// (레이플이 없다)-(int) g:(int) x :(int) y; //성공: 두개의 익명의 레이블이 있다.-(int) g:(int) x :(float) y; //에러: 이 메소드는 위의 메소드와 동일

//-(int) g:(int) x andY:(int) y; //성공: 두번째 레이블이 "andY"이다-(int) g:(int) x andY:(float) y; //에러: 이 메소드는 위의 메소드와 동일

//-(int) g:(int) x andAlsoY:(int) y; //성공: 두번째 레이블이 "andAlsoY"로

// "andY"와 다르다

@end

레이블을 사용하여 메소드를 식별하는 방법은 아래의 표현된 것처럼 함수의 정확한 “이름”을표현하는데 유용하다.

@interface Foo : NSObject {}

//메소드 이름은 "g"이다-(int) g;

//메소드 이름은 "g:"이다-(int) g:(float) x;

//메소드 이름은 "g::"이다-(int) g:(float) x :(float) y;

//메소드 이름은 "g:andY:"이다-(int) g:(float) x andY:(float) y;

//메소드 이름은 "g:andZ:"이다-(int) g:(float) x andZ:(float) z@end

명백히 어떤 두 개의 Objective-C 메소드는 자료형(type)이 아닌 레이블(label)을 이용해서 구분한다. 이런 방식으로 Objective-C에서는 “멤버 함수에 대한 포인터”가 아니라 선택자(selector)를이용하여 메소드를 가리키게 된다12. 여기에 대한 설명은 17쪽 3.3.5절에서 이뤄진다.

11ANSI 표준화 이후 크게 진전되지 않던 C의 표준 개정 작업이 1990년대에 활발히 진행되었는데, 90년대 후반의 개정작업들이 ISO/IEC 9899:1999에 정리되어 출간되었다. 이 표준을 따르는 C 규범을 C99라 부른다. [譯註]

12함수 포인터를 부연 설명하자면 함수 포인터의 자료형(type)은 당연히 포인터 형이다. 함수 포인터가 일반적인 포인터와 다른 점은 변수의 주소 값이 아니라 함수의 주소 값을 저장한다는 것 뿐이다. 함수는 사실상 코드의 한 부분인데,여기서 코드라는 것은 프로그래머가 프로그래밍 언어를 이용하여 작성한 프로그램이 컴파일(compile)되어 메모리에 탑재될 수 있는 기계어 형태로 변환된 것을 의미한다. 함수가 시작되는 부분에 대응하는 코드가 메모리 공간의 어느 곳에자리를 잡고 있는지 알려주는 주소값을 저장하는 것이 바로 함수 포인터이다. C 언어는 함수 자체를 변수로 만들 수 없기때문에 함수 포인터를 통하여 포인터가 가리키는 곳의 함수를 실행시킬 수 있다. [譯註]

16

Page 17: C++에서 Objective-C까지 번역자료

3.3.5 멤버 함수에 대한 포인터: 선택자(Selector)

Objective-C에서메소드는괄호와레이블을사용하는독특한문법을갖는다.이문법으로 (C언어의표준적인)함수를선언하는것은불가능하다.함수에대한포인터의개념은 C와 Objective-C가모두 동일하지만, 메소드에 대한 포인터에 대해서는 두 언어에 차이가 있다.

C++에서 메소드에 대한 포인터를 쓰기 위한 구문법(syntax)은 까다롭기는 하지만, C 언어의함수 포인터와 동일하다. 포인터 구문의 초점이 자료형(type)에 맞춰져 있다.

C++

class Foo{public:int f(float x) {...}

};

Foo barint (Foo::*p_f)(float) = &Foo::f; // Foo::f에 대한 포인터

(bar.*p_f)(1.2345); //bar.f(1.2345) 호출과 동일함

Objective-C에서는 메소드에 대한 포인터를 위해 새로운 자료형을 도입하였다. 이 “메소드의포인터”를 selector라고부른다.이자료형이 SEL인데,그값은 (매개변수레이블과함께)메소드의정확한이름에대한 @selector를사용하여계산한다. NSInvocation클래스footnoteNSInvocation클래스는 객체나 응용 프로그램간에 메시지를 포워딩하거나 저장하는데 사용되는 클래스이다.NSInvocation 객체는 Objective-C의 메시지, 타겟, 선택자, 매개변수, 반환 값등 모든 요소를 포함할 수 있다 [譯註]를 사용하여 메소드를 호출할 수 있다. 대부분의 경우 (NSObject로부터 상속받은)performSelector: 계열의 유틸리티 메소드가 간편하기는 하지만, 다소 제약이 있다. 가장단순한 형태의 performSelector:로는 다음의 세 가지 예가 있다.

-(id) performSelector:(SEL)aSelector;-(id) performSelector:(SEL)aSelector withObject:(id)anObjectAsParameter;-(id) performSelector:(SEL)aSelector withObject:(id)anObjectAsParameter

withObject:(id)anotherObjectAsParameter;

이때 반환값(returned value)은 호출되는 메소드가 반환하는 값과 형태와 동일하다. 메소드의 매개변수가 객체가 아닌 (즉, float, int 등과 같이 값 형식일) 경우는 객체 형태로의 변환을 위해 보통 NSNumber 같은 래퍼(wrapper) 클래스를 사용해야 한다. 더욱 일반적이고 강력한NSInvocation 클래스를 사용할 수도 있다 (관련문서를 참조).앞서언급한바와같이객체에구현되어있지않은메소드라할지라도이메소드를호출하는것

을막을방법은없다.사실메소드는메시지가수용될수있는경우에만실제로구동된다.그러나객체가메소드를모르는경우라도처리가능한예외가발생할뿐응용프로그램자체가중단되는일

은 없다. 게다가 객체가 어떤 메소드를 구동할 수 있는지를 확인하기 위해 respondsToSelector:를 사용할 수도 있다. @selector() 값은 컴파일시 계산되기 때문에 실행코드를 느리게 만들지는않는다.

17

Page 18: C++에서 Objective-C까지 번역자료

Objective-C@interface Slave : NSObject {}-(void) readDocumentation:(Document*)document;

@end

// 10개의 slave 배열 "array[]"와// 도큐멘트 "document"를 가정해 보자

//일반적인 메소드 호출방식

for(i=0 ; i<10 ; ++i)[array[i] readDocumentation:document];

// readDocument를 직접 호출하는 대신 performSelector를 사용해본다

for(i=0 ; i<10 ; ++i)[array[i] performSelector:@selector(readDocumentation:)

withObject:document];

// 선택자의 형은 SEL이다.// 다음 버전은 이전과 달리 컴파일시에 @selector가 평가되므로

// 그다지 효율적이지는 않다.SEL methodSelector = @selector(readDocumentation:);for(i=0 ; i<10 ; ++i)[slaves[i] performSelector:methodSelector withObject:document];

// 객체 "foo"의 형이 미리 알려지지않은 (id)형일때// 검사자체는 필수사항이 아니다. 그러나 만일

// 객체가 readDocumentation: 를 가지지 않은 경우에는

// 예외가 발생할 수 있으므로 이를 방지하는 것이 좋다.if ([foo respondsToSelector:@selector(readDocumentation:)])[foo performSelector:@selector(readDocumentation:) withObject:document];

이제 선택자는 매우 간단한 함수형 파라미터로 사용할 수 있다. 정렬(sorting)과 같은 범용알고리즘은 이러한 방식으로 쉽게 특화될 수 있다(비고. 57쪽 11.3절).엄밀히 이야기 하자면, 선택자는 함수에 대한 포인터가 아니다. 선택자의 실제 자료형은 실행

환경에 의해 메서드 식별자로 등록된 C 문자열이다. 클래스가 로드될 때마다, 그 클래스의 메소드가 자동으로 테이블에 등록되어 @selector()가 동작할 수 있게 된다. 이런 방법을 사용하기때문에 두 개의 선택자를 비교할 때 문자열을 비교하는 방식이 아니라 오히려 ==(값 비교)연산을이용해서 주소값이 같은지를 비교하는 방식을 사용할 수 있는 것이다.메소드를 C 함수처럼 보고 선택자가 아니라 IMP13 자료형을 이용하여 다른 방식으로 주소를

얻을 수도 있는데, 여기에 대해서는 57쪽 11.3.2절에서 간단히 설명할 것이다. 이 방식은 거의사용되지 않고 최적화 용도로 드물게 사용할 뿐이다. 예를 들어 가상 호출은 IMP가 아닌 선택자에 의해 처리된다. C++언어에서 지원하는 함수 포인터에 대한 Objective-C의 동등한 대응물은선택자(selector)인 것이다.마지막으로독자여러분은 C++언어에있는 this와같은역할을하는 Objective-C언어의 self를

기억하고 있을 것이다. 이 self는 현재 객체를 저장하기 위하여 모든 메소드마다 숨겨두고 사용할수 있는 매개변수이다. 또 다른 숨겨진 매개변수 _cmd도 있다는 점에도 유의하기 바란다. 이 _cmd매개변수는 (객체가 아니라) 현재 메소드 자체를 말한다. (이 _cmd를 performSelector:와 함께활용하여 다음과 같에 매우 객체지향적인 재귀호출을 만들 수 있다. - 역자 추가 설명)

13 IMP는 일종의 함수 포인터인데, 반환값은 id 형이고, 매개변수로는 id, 선택자(SEL), 가변 인자 등을 받는다.예를 들면 typedef id (*IMP)(id self,SEL ,...);와 같은 형태로 선언된다. IMP는 Objective-C함수를 실행시간

에 호출하기 위해 사용되는데, IMP 값을 획득하기 위해서는 다음과 같은 구문을 사용한다: IMP myImp = [myObjectmethodFor:mySel];

18

Page 19: C++에서 Objective-C까지 번역자료

Objective-C@implementation Foo

-(void) f:(id)parameter //C 함수에서는 다음과 같다.//"void f(id self, SEL _cmd, id parameter)"

{id currentObject = self;SEL currentMethod = _cmd;[currentObject performSelector:currentMethod

withObject:parameter]; //재귀호출[self performSelector:_cmd withObject:parameter]; //위의 문장과 동일

}

@end

3.3.6 매개변수(parameter)의 디폴트 값

Objective-C는함수나메소드의매개변수에디폴트값을지정하는것을허용하지않는다.몇몇파라미터가 선택적(optional)으로 사용된다면 각각의 선택 상황에 해당하는 모든 함수를 생성해야한다. 14 생성자의 경우, 지정 초기화(designated initializer) 개념을 사용해야 한다( 33쪽 5.1.7절).

3.3.7 가변 개수의 인자

Objective-C는 Objective-C는인자의개수가가변적인메소드를사용할수있다. C에서처럼,구문법은 마지막 인자로 “...” 사용하게 된다. 많은 코코아(Cocoa) 메소드가 이런 방식을 사용하기는하지만 크게 유용한 것은 아니다. 더 자세한 내용은 Objective-C 기술 문서를 참고하라.

3.3.8 익명 인자

C++에서는 어떤 함수의 원형(prototype)을 기술할 때, 매개변수에 대해 이름을 부여하지 않아도해당 매개변수의 자료형만 기술되어 있다며 함수의 특징을 규정하는 데에 충분하므로 문제가

되지 않는다. 하지만, Objective-C에서는 이런 방식이 불가능하다.

3.3.9 함수 원형(prototype) 한정어(modifier) - const, static, virtual, “= 0”, friend,throw

C++에서 함수 원형(prototype)에 몇 가지 한정어(modifier)를 부가할 수 있다. 그러나 Objective-C에서는 이런 한정어를 사용할 수 없다. 사용할 수 없는 한정어 목록은 아래와 같다.

• const :메소드는 const로설정할수없다.결과적으로는 mutable키워드도존재할수없다.

• static : 인스턴스 메소드와 클래스 메소드의 구분은 함수 원형 앞에 “-” 또는 “+”를 붙여구별한다. static 메소드는 “+” 기호로 표시한다.

• virtual : Objective-C메소드는가상이기때문에이런키워드가필요없다. C++의순수가상

메소드 는 공식 프로토콜(protocol)이라는 개념으로 구현한다(비고. 23쪽 4.4절).

• friend : Objective-C에는 “프렌드(friend)” 개념이 없다.

• throw : C++에서는 메소드가 몇 개의 예외만을 전송할 수 있도록 제한할 수 있지만, 이것은Objective-C에서는 불가능하다.

14C++는 디폴트 파라미터를 지정하여 매개변수가 없을 경우 디폴트 매개변수와 그 매개변수의 값을 자동으로 생성할

수 있는 편의성을 제공하지만, 이것이 원치않는 값을 설정하여 찾기 힘든 버그를 생성할 가능성도 있다. [譯註]

19

Page 20: C++에서 Objective-C까지 번역자료

3.4 메세지와 전송

3.4.1 nil에게 메시지 보내기

아무런 옵션 설정이 없으면 nil에 메시지를 보내는 것(메소드를 호출하는 것)이 기본적으로 허용된다. 이때 메시지는 그냥 무시된다. 이렇게 하면 널(null) 포인터인지 아닌지를 검사하는 코드를작성하지않아도되기때문에코드가매우간략하게정리된다. GCC에서는옵션설정을통해이런편리한 기능을 막아버릴 수도 있는데, 이것은 프로그램을 더욱 최적화하기 위한 것이다.

3.4.2 알려지지 않은 객체에 메시지 위임

위임(델리게이션)은 코코아(Cocoa)에 있는 사용자 인터페이스 요소들(테이블, outlines...)에서는매우 일반적으로 사용되는 개념인데, 미지의 객체에게 메시지를 보낼 수 있는 능력을 활용한다.예를 들어, 어떤 객체는 일부 작업을 조력자(assistant) 객체에게 위임할 수 있다.

//assistant를 정의하는 메소드

-(void) setAssistant:(id)slave{[assistant autorelease]; //메모리 관리에 관한 설명 참조

assistant = [slave retain];}

//performHardWork 메소드는 델리게이션을 사용할 수 있다

-(void) performHardWork:(id)task{//assistant가 performHardWork 메소드를 모르기 때분에

//우리는 이 객체가 메시지를 처리할 수 있는지 미리 검사해야한다.if ([assistant respondsToSelector:@selector(performHardWork:)])[assistant performHardWork:task];

else[self findAnotherAssistant];

}

3.4.3 포워딩(forwarding): 알 수 없는 메시지 다루기

C++에서는객체가구현하지않은메소드를호출하는경우에코드가컴파일되지않는다. Objective-C에서는 조금 다른데, 객체에 어떤 메시지든 보낼 수 있다. 보내어진 메시지가 처리될 수 없는것이라면 그냥 무시해 버린다 (물론 예외(exception)는 발생한다). 15 뿐만 아니라, 이 메시지를무시하지 않고 다른 객체에 포워딩(forwarding)하는 일까지 가능하다.컴파일러가 어떤 객체의 자료형(type)을 알고 있다면, 메시지를 보내는 일 - 즉, 메소드를 호

출하는 일이 실패할 것인지 미리 알 수 있고, 경고를 발생시킬 수 있다. 하지만, 이것은 컴파일이불가능한 오류가 아니기 때문에, 이런 경우에는 해당 메시지를 처리할 대안을 선택할 수 있다.이러한 두 번 째 선택은 forwardInvocation: 메소드를 호출하는 방식으로 표현되는데, 이 메소드는가장최근에발생한메시지를다른곳으로연결한다.이것은명백히 NSObject의메소드인데,디폴트로는 아무런 일을 수행하지 않는다. 조력자 객체를 관리하는 새로운 방식은 다음과 같다.

15 구현되지 않은 메소드를 호출하려는 메시지까지 전달할 수 있는 특징은 Objective-C 프로그램을 매우 유연하게 만들어주지만,분명한단점도가지고있다.그단점은구현되지않은메소드를실수로호출할경우컴파일시간에검출되지않고 실행시에 비로소 알 수 있기 때문에 프로그래머의 세심한 주의가 요구된다는 것이다. [譯註]

20

Page 21: C++에서 Objective-C까지 번역자료

-(void) forwardInvocation:(NSInvocation*)anInvocation{//만일 이 메소드로 이동했다면, 그 이유는 객체가

//호출(invocation) 메시지를 처리하지 못했기 때문이다.//이때 "anInvocation" 객체에 "selector" 메시지를 보내어 봄으로써,//해당 선택자를 얻어 다른 객체에 처리를 넘길 수 있다.if ([anotherObject respondsToSelector:[anInvocation selector]])[anInvocation invokeWithTarget:anotherObject];

else //그래도 안 되면 부모클래스로 시도해보는 것도 잊지 말자

[super forwardInvocation:anInvocation];}

어떤 메시지가 바로 처리되지는 않더라도 최종적으로 forwardInvocation:에서 처리될 수있다. 그러나 이런 경우에도 respondsToSelector:를 통해 검사를 하면 여전히 NO를 리턴할 것이다.사실 respondsToSelector:메커니즘은 forwardInvocation:의작동할지판단하는것까지는고려하지 않았다.호출 포워딩은 오류가 발생해야 할 때에 어떤 코드를 수행하는 것이기 때문에 좋지 않는 코딩

방식이라 생각할 수도 있다. 그러나 사실 이러한 메커니즘을 아주 좋은 방식으로 활용할 수 있는데, 코코아의 NSUndoManager16가 대표적인 예이다. 이 클래스는 매우 유쾌한 구문을 가능하게한다:이실행취소관리자는메소드에대한호출을기록할수있다.이구문은극히예외적으로유쾌한 문법을 허용하는데, 이 실행취소 관리자는 메소드 호출들이 있을 때, 자기 자신이 그 대상이아님에도 불구하고 모든 호출을 기록할 수 있다.

3.4.4 하향 형변환(downcasting)

하향 형변환은 C++에서하위클래스의메소드를호출할때필요한데,부모클래스포인터만알고있는 상황이라면, dynamic_cast를 이용하면 된다. Objective-C에서 이런 방식이 필요 없는데, 그이유는 Objective-C에서는 객체가 처리할 수 없는 메시지라도 얼마든지 보낼 수 있기 때문이다.17

그러나, 컴파일러 경고를 피하기 위하여, 단순히 객체 타입을 형변환(cast)시킬 수도 있다.Objective-C에는 명시적으로 사용하는 별도의 하향 형변환 연산자가 없기 때문에 C 언어의 전통적인 형변환 구문을 그대로 사용할 수 있다.

//NSMutableString은 문자열의 교체가 가능한

//NSString의 하위 (문자열) 클래스이다.//"appendString:"은 NSMutableString에서만 지원된다.NSMutableString* mutableString = ...initializing a mutable string...NSString* string = mutableString;//NSString 포인터 변수내에 저장

//아래의 세 가지 메소드 호출은 모두 사용가능함.[string appendString:@"foo"];//컴파일시 경고

[(NSMutableString*)string appendString:@"foo"];//경고 없음

[(id)string appendString:@"foo"];//경고 없음

16NSUndoManager 클래스는 실행취소(undo)와 재실행(redo) 연산을 위해 만들어진 범용 레코더이다.17하향 형변환을 이렇게 간단하게 할 수 있는 동적인 특성은 Objective-C가 가지는 큰 장점이다. 이러한 특성 때문에

Objective-C에서는 모든 객체를 간단히 최상위 클래스 포인터인 id형으로 다룰 수 있다. [譯註]

21

Page 22: C++에서 Objective-C까지 번역자료

4 상속

4.1 단순 상속

Objective-C는 상속 개념을 잘 구현하고 있으나, 모호성 문제를 유발하는 다중 상속을 지원하지는 않는다(사실 Java, C# 등의 객체지향언어들도 이 문제 때문에 다중 상속을 지원하지 않고

interface라는 방식을 사용한다). 이 때문에 발생하는 제약사항은 이 문서의 뒷부분에 설명하는다른 방법인 프로토콜, 클래스 카테고리등으로 대치한다(23쪽 4.4절, 27쪽 4.5절).

C++ Objective-C

class Foo : public Bar,protected Wiz

{}

@interface Foo : Bar //단일 부모를 통한

//상속만을 허용함

//Wiz라는 부모의 기능에 대한 상속이 필요함.//따라서 이 한계를 대체하는 기법이 필요하다

{}@end

C++에서 클래스는 public, protected, private 모드 중 하나의 방식으로 부모 클래스로부터상속을받을수있다.메소드에서는스코프해석연산자:: (Bar::, Wiz::)를사용하여슈퍼클래스를참조할 수 있다.

Objective-C에서는 public 모드에서 하나의 클래스로부터만 상속받을 수 있다. Java 언어와유사하게 super 키워드(사실 Objective-C에서 super는 키워드가 아닌 메소드임)를 이용해서 슈퍼클래스를 참조할 수 있다.

4.2 다중 상속

Objective-C는 다중 상속을 구현하지 않지만 다른 개념인 프로토콜(비고. 23쪽 4.4절) 과 카테고리(비고. 27쪽 4.5절)를 채택하여 이 개념을 구현했다.

4.3 가상성

4.3.1 가상 메소드

C++에서 가상 메소드는 메소드가 사용되기 전에 사용되기 때문에 "가상(virtual)"이라는 이름을붙이게 되었다. 가상 메소드는 컴파일러에게 "객체가 인스턴스화된 이후에 메소드의 구현부를가져오라"는 지시로 이해하면된다. 이러한 기법을 "사후 바인딩(late binding)" 또는 "동적 바인당(dynamic binding)"이라고도 한다. C++와는 달리 모든 객체가 동적으로 할당되는 특성을 가진

Objective-C에서 모든 메소드 역시 가상이다. 따라서, 특별히 가상 메소드 선언을 위한 virtual키워드는 존재하지 않으며 존재할 필요도 없다.

4.3.2 가상 메소드의 암묵적인 재정의

Objective-C에서는 interface 절에 선언되지 않은 메소드를 implementation 절에서 구현하는 것도 가능한다. 이 기능을 이용하여 메소드에 대한 “숨기기” 기능을 어느정도 달성할 수 있지만,@private 개념을 완벽히 대체하지는 못한다(Objective-C에서는 메소드가 모두 public이라 C++의

private메소드는존재하지않는다).그러나이방식은인터페이스선언을간단하게하는데도움이된다.선언을하지않고도구현부에서구현하는방법은절대나쁜방법은아니다.심지어 Objective-C

의슈퍼클래스에있는 “잘알려진”메소드들도이기법을사용한다.루트클래스 NSObject의많은메소드는 암시적으로 재정의된다. 생성자 init (비고. 29쪽 5.1절), 소멸자 dealloc (비고. 35쪽5.2절), 뷰(View) 객체의 드로잉 메소드 drawRect:가 대표적인 예인데 이밖에도 많은 메소드가있다.이 방식의 경우 비록 부모 객체에서 재정의된 부분이 무엇인가를 살펴보는 것이 까다롭기는

하지만 이러한 재정의는 선언부를 작고 읽기 쉽게 만드는데는 도움이된다.

22

Page 23: C++에서 Objective-C까지 번역자료

순수가상메소드의개념(하위클래스에서반드시재정의해야하는메소드)은공식프로토콜의개념을 통해 해결하였다(비고. 23쪽 4.4.1절).

4.3.3 가상 상속

사실상 Objective-C에는 다중 상속이 없기 때문에 이와 관련된 문제도 없으므로, 가상 상속은Objective-C와는 관련이 없다.

4.4 프로토콜

Java와 C#은 인터페이스 개념을 이용하여 다중 상속을 지원하지 않음으로서 발생하는 문제를해결한다. Objective-C에서도 같은 개념이 사용되는데 이를 프로토콜이라 부른다. C++에서 이것

에 해당하는 것이 추상 클래스이다. 프로토콜은 실제 클래스가 아니다. 메소드만 선언하고, 어떤데이터든지 가질 수 있는데, 이러한 프로토콜에는 두 가지 프로토콜이 있다. 이 프로토콜을 공식 프로토콜, 비공식 프로토콜이라 한다.

4.4.1 공식 프로토콜

공식프로토콜은해당클래스에서필수적으로구현되어야하는메소드들의집합이다.이것은특정서비스에 필요한 모든 것을 처리할 수 있는지 확인하는 클래스에 대한 인증으로 보일 수 있다.클래스는 프로토콜의 갯수 제한을 받지 않는다.

C++

class MouseListener{public:virtual bool mousePressed(void) = 0; //순수 가상 메소드

virtual bool mouseClicked(void) = 0; //순수 가상 메소드

};

class KeyboardListener{public:virtual bool keyPressed(void) = 0; //순수 가상 메소드

};

class Foo : public MouseListener, public KeyboardListener {...}

//Foo는 반드시 mousePressed, mouseClicked, keyPressed를 구현해야함

//Foo는 마우스와 키보드를 위한 이벤트 리스너로 사용가능함

23

Page 24: C++에서 Objective-C까지 번역자료

Objective-C

@protocol MouseListener-(BOOL) mousePressed;-(BOOL) mouseClicked;

@end

@protocol KeyboardListener-(BOOL) keyPressed;

@end

@interface Foo : NSObject <MouseListener, KeyboardListener>{...}@end

//Foo는 반드시 mousePressed, mouseClicked, keyPressed를 구현해야함

//Foo는 마우스와 키보드를 위한 이벤트 리스너로 사용가능함

C++에서 프로토콜은 추상 클래스와 순수 가상 메소드에 의해 구현된다. C++에서 추상클래스는

데이터를 포함할 수 있기 때문에 Objective-C 보다 더 강력하다18.

Objective-C에서 프로토콜은 독특한 문법을 갖는다. 각진 괄호<...>를 사용한 문법은 C++템

플릿(템플릿은 Objective-C에서 존재하지 않는다)과 아무 상관이 없다.Objective-C는클래스헤더부에메소드선언을하지않아도프로토콜의모든메소드를구현할

수있다.이경우에, conformsToProtocol::메소드는 NO를리턴한다.효율상의이유로, conformsToProtocol:는 메소드별로 프로토콜의 적합성을 검사하지 않으며, 개발자가 명시적으로 선언한 것에 따

른다. 그러나, 프로토콜의 메소드가 호출되는 경우 conformsToProtocol:의 부정적인 응답이

오더라도 프로토콜의 메소드가 호출되기만 하면 프로그램은 올바르게 동작한다. 다음 문장은conformsToProtocol:의 프로토타입이다.

-(BOOL) conformsToProtocol:(Protocol*)protocol//프로토콜 객체가 @protocol(protocol name) 호출에 의해 반환된다

공식 프로토콜을 준수하는 객체는 프로토콜 자체의 이름을 각진 괄호 사이에 추가될 수 있다.이 방식은 assertion에서 유용한다. 예를 들면 다음과 같다.

//다음의 표준 코코아 메소드는 임의의 type (id) 형을 가지는

//하나의 파라미터를 가질 수 있다. 그러나 NSDraggingInfo 프로토콜을//준수해야 한다.-(NSDragOperation) draggingEntered:(id <NSDraggingInfo>)sender;

4.4.2 선택적 메소드

클래스가 특정 서비스를 처리하도록하기 위해 프로토콜을 따르는 것이 바람직하겠지만, 모든 프로토콜을 사용하도록 강요하지는 않는다. 코코아 프레임워크에서 delegate 개념은 객체에 특별한기능을 부가적으로 수행하도록하기 위해 널리 활용된다. 이때 이 객체는 프로토콜의 모든 작업을수행하는것이 아니라 일부 작업만을 선택적으로 할 수 있다.직관적인 해결책으로는 공식 프로토콜을 여러 개의 프로토콜로 분할한 후, 클래스로 하여금

이러한프로토콜중하나를따르도록하는방법이가능할것이다.그러나이방법은그다지편리하지 않기 때문에 코코아는비공식 프로토콜(informal protocols)의 개념을 도입하여 이를 해결한다.Objective-C 1.0은 비공식 프로토콜을 사용할 수 있다(비고. 25쪽 4.4.3절). Objective-C 2.0부터는 새 키워드인 @optional과 @required을 도입하여 선택 메소드와 필수 메소드 사이의 구별을두었다.

18위의 코드는 사실 Java 언어의 이벤트 처리를 담당하는 이벤트 리스너의 전형적인 구조이다. Java 개발자라면 Java언어와 비교하면 매우 쉽게 Objective-C와의 차이를 이해할 수 있을 것이다

24

Page 25: C++에서 Objective-C까지 번역자료

@protocol Slave

@required //필수 부분

-(void) makeCoffee;-(void) duplicateDocument:(Document*)document count:(int)count;

@optional //선택 부분

-(void) sweep;

@required //필수/선택 절을 구분할 수 있다

-(void) bringCoffee;

@end

4.4.3 비공식 프로토콜

비공식프로토콜(informal protocol)은진짜 “프로토콜”이아니며,프로그램코드에아무런강제적인제약조건도만들지않는다.그러나그것은본래 “비공식(informal)”인데코드의자동문서화를목표로한다.비공식 프로토콜은 개발자가 응용 분야에 따라 메소드를 묶을 수 있도록 하며, 이를 통해 클래

스들을 일관성있게 조직할 수 있도록 한다. 이 기능을 통해 클래스를 일관성있게 조직화시킬 수있다.이런 이유로 비공식 프로토콜은 공식 프로토콜을 느슨하게 만들려고 만들어진 것이 아니라는

것은 놀랄일이 아니다. 이를 위해 클래스 카테고리라는 새로운 개념이 사용된다(비고. 27쪽 4.5절).앞서 살펴본 예제인 노예(slave) 클래스에 대해 “문서 관리”라는 서비스를 추가하는 것을 상상

해보자. 이 서비스에서는 녹색, 청색, 적색 문서 서비스간의 차이가 있다는 가정을 함께 해보자.비록 이 클래스가 청색 문서만을 처리할 수 있더라도, Slave 클래스는 manageGreenDocuments,manageBlueDocuments 및 manageRedDocuments 3개의 공식적 프로토콜을 사용하는 것을 더 선호한다. Slave 클래스에 카테고리 DocumentsManaging을 추가하는 일을 하는 메소드를 선언하기위해, 이 클래스를 상속하는 수직적 확장대신 수평적 확장기능인 카테고리가 유용할 것이다. 이카테고리의 이름은 괄호안에 지정한다(보다 자세한 설명은 다음의 절을 참조하라 27쪽 4.5절).

@interface Slave (DocumentsManaging)-(void) manageBlueDocuments:(BlueDocument*)document;-(void) trashBlueDocuments:(BlueDocument*)document;@end

어떠한 클래스라도 이 서비스와 관련있는 메소드를 선언하기 위해 DocumentsManaging 카테고리를 사용할 수 있다.

@interface PremiumSlave (DocumentsManaging)-(void) manageBlueDocuments:(BlueDocument*)document;-(void) manageRedDocuments:(RedDocument*)document;@end

개발자는코드를브라우징하여 DocumentsManaging카테고리를볼수있다.이에따라클래스가 몇 가지 작업에 유용한지 예측하여, 문서를 컨설팅하여 정확하게 작업을 확인할 수 있다. 소스코드를 확인하지 않더라도, 런타임시에 메소드가 존재하는지 조회하는 기능은 여전히 가능한다.

if ([mySlave respondsToSelector:@selector(manageBlueDocuments:)])[mySlave manageBlueDocuments:document];

25

Page 26: C++에서 Objective-C까지 번역자료

엄밀히 말하면, 프로토타입에 관한 지식과는 별도로 비공식 프로토콜은 컴파일러에게는 쓸모가 없으며 이것이 객체의 사용을 제한하지 않는다. 그러나, 이 방법은 코드의 자체 문서화를통해서 API를 좀 더 읽기 쉽게 만들어준다.카테고리는 또한 인스턴스 변수를 추가할 수 없다는 한계가 있으며, 이름 충돌을 방지하는

기능이 없으므로 이 부분에서의 주의가 필요하다.

4.4.4 protocol 타입 객체

객체의형태를통해서구현되는(또는인스턴스되는)클래스와같이,프로토콜은실행시에 Protocol*형으로 표현된다. 이러한 객체는 conformsToProtocol:과 같은 메소드의 매개변수로 사용될 수있다(비고. 65쪽 13.1.2절).키워드 @protocol은 프로토콜을 선언하는데 사용되며, Protocol* 객체가 이것에 대한 참조

객체의 이름으로 선언될 수 있다.Protocol* myProtocol = @protocol(protocol name ).

4.4.5 분리된 객체를 위한 메시지 자격심사

Objective-C의 동적 성질 때문에 분리된 객체들끼리도 쉽게 통신할 수 있다. 이 객체들은 서로다른프로그램에소속되어있을수있지만,공동의작업을위임받고정보를교환할수있다.이제,공식 프로토콜은 어디서 왔던간에 이 객체가 주어진 서비스에 따른다는 것을 보증하는 완벽한

방법이다. 공식 프로토콜 개념은 분리된 객체들이 보다 효과적으로 통신할 수 있도록 하기 위해몇몇 추가 키워드를 제공하고 있다.이러한키워드들이 in, out, inout, bycopy, byref, oneway이다.이키워드들은분산객체에서

프로토콜 정의의 외부에서만 적용할 수 있으며, 예약된 키워드가 아니기 때문에 편하게 사용할수 있다.이키워드는프로토콜의행동에대하여추가정보를추가하고형식적프로토콜내부에선언된

메소드 원형 안에 삽입된다. 그들은 input 파라미터가 어느 파라미터이고, ouput 결과는 어떤것인지 지정하기 위하여 이용될 수 있다. 그들이 사본 또는 참고에 의해 사용되었는지 알려주는 것도가능한다. 그리고 메소드가 동기화되도록할지 아닌지를 알려주는 것도 가능하다.다음에 다양한 의미를 설명한다.

• in 파라미터는 입력 변수이다.

• out 파라미터는 출력 변수이다.

• inout 파라미터는 두 가지 방식(입력과 출력)으로 사용될 수 있다.

• bycopy 파라미터는 복사에 의해 전송된다.

• byref 파라미터는 참조(복사가 아닌)에 의해 전송된다.

• oneway메소드(결과를곧바로예상할수없다)는비동기방식이다.따라서 void를리턴해야한다.

예를 들어, 다음은 객체를 반환하는 비동기 메소드이다.

-(oneway void) giveMeAnObjectWhenAvailable:(bycopy out id *)anObject;

디폴트로 파라미터는 const 포인트의 경우 in으로 선택되며, 그외의 경우는 모두 inout으로 지정된다. inout대신 in이나 out을선택하는것이최적화에유리하다.파라미터를전송하는디폴트모드는 byref이며, 메소드는 기본적으로(oneway 제외) 디폴트로 동기된다.

포인터가 아닌 값에 의해 파라미터가 전송될 경우 out, inout은사용할수없고, in만가능하다.

26

Page 27: C++에서 Objective-C까지 번역자료

4.5 클래스 카테고리

클래스에 대한 카테고리는 클래스의 구현부를 여러 부분으로 나눌때 사용하는 방법이다. 즉 매우큰 클래스의 개발할 경우에 여러명의 개발자가 클래스의 구현을 나누어하거나, 이미 개발된 클래스를 상속하지 않고도 새로운 기능을 추가하는 Objective-C의 매우 독특하고도 편리한 기능이다.이때 각 카테고리는 그 클래스의 일부이다. 클래스는 카테고리를 임의의 갯수만큼 사용할 수 있으나, 그 어느 것도 인스턴스 데이터는 추가할 수 없다. 클래스 카테고리는 다음과 같은 장점을제공한다.

• 세심한 개발자를 위해, 메소드의 그룹을 만드는 것이 가능한다. 아주 다채로운 클래스를위해, 다른 역할을 하는 코드를 완전히 분리할 수 있다.

• 한 클래스에 대하여 협업 작업을 하는 경우 별도 컴파일이 가능하다.

• 카테고리의인터페이스와그것의구현파일이존재하는경우(.m 파일),파일내부에표시되는 private 메소드를 정의하는 것이 아주 쉽다(비록 외부에서 아무 객체나 메소드의 프로토타입을 알고있어 사용할 수 있고, 호출 제한이 없어도). 이 카테고리를 위한 적당한 이름은FooPrivateAPI가 될 수 있다.

• 클래스내의 동일한 코드를 복제하지 않고 별도의 응용프로그램에서 확장시킬 수 있다. 따라서 어떤 클래스도 기존의 코코아 클래스를 확장시킬 수 있다.

마지막 포인트는 아주 중요하다. 모든 개발자는 개발을 위해 필요한 메소드로 표준 클래스를확장할 수 있는 선택권을 가진다. 하지만 상속 역시 클래스를 확장하는 대표적인 방법이다. 그러나, 간단한 기능만을 상속하는 컨텐츠의 경우를 생각해보면 간단한 기능을 위해서 무거운 하위클래스 구조를 만드는 것이 바람직하지 않을 것이다. 더욱이, 단 하나의 메소드만을 확장하기 위해서 그 클래스의 하위 클래스를 만드는 것은 불필요한 노력으로 볼 수 있다. 클래스 카테고리는이 문제를 위한 멋지고 우아한 해결방법이다.

C++ Objective-C

class MyString : public string{public://모음의 갯수를 계산

int vowelCount(void);};

int MyString::vowelCount(void){...}

@interface NSString (VowelsCounting)//{}을 사용하지 않았다는 것에 유의

-(int) vowelCount; //모음의 갯수를 계산

@end

@implementation NSString (VowelsCounting)-(int) vowelCount{...}@end

C++에서는 새로운 클래스는 제한없이 사용할 수 있다.

Objective-C에서 (표준 코코아 클래스인)NSString 클래스는 전체 프로그램 안에서 사용할 수있는 확장 기능을 갖게 된다. 이때 새 클래스는 만들어지지 않는다. 모든 NSString 객체는 확장기능에서 혜택을 받는다.(심지어 문자열 상수라 할지라도 기능의 혜택을 받는다, 비고. 54쪽 9.1절) 하지만 인스턴스 변수는 카테고리 안에 추가될 수 없기 때문에 {. . . } 블록이 없다.카테고리도 “사적인 사용”을 위해 완벽히 익명으로 될 수 있다.

27

Page 28: C++에서 Objective-C까지 번역자료

@interface NSString ()//{}을 사용하지 않았다는 것에 유의

-(int) myPrivateMethod;@end

@implementation NSString ()-(int) myPrivateMethod{...}@end

4.6 프토토콜, 카테고리, 서브클래싱의 통합 사용프로토콜, 카테고리, 파생기능을 함께 사용할때의 유일한 제약 조건은 하위 클래스와 카테고리가동시에 선언될 수 없으므로 두 개의 별도의 단계가 필요하다는 것이다.

@interface Foo1 : SuperClass <Protocol1, Protocol2, ... > //ok@end

@interface Foo2 (Category) <Protocol1, Protocol2, ... > //ok@end

//아래 : 컴파일 오류

@interface Foo3 (Category) : SuperClass <Protocol1, Protocol2, ... >@end

//해결책@interface Foo3 : SuperClass <Protocol1, Protocol2, ... > //1단계@end@interface Foo3 (Category) //2단계@end

28

Page 29: C++에서 Objective-C까지 번역자료

5 인스턴스화

일반적으로클래스를인스턴스화시킬때두가지의주요문제가부각된다. C++의생성자/소멸자/복사 연산자 개념이 Objective-C에서 어떻게 구현되고, 실행되고, 메모리에서 관리되는가?첫째로, 중요한 포인트는 C와 C++언어에서는, 변수는 디폴트로 “자동(automatic)” 값으로 설

정된다는 점이다. C와 C++ 언어는 static 선언을 하지않는 한 이 변수들은 자신이 정의된 블록내부에만 유효하다. 그리고 동적으로 할당된 메모리는 free() 함수나 delete 메소드를 만나기전까지는 사용할 수 있다. 모든 객체는 C++ 언어에서도 이 규칙을 따른다.그러나, Objective-C에는 모든 객체는 동적으로 할당된다는 규칙이 있다. 이 점에서 C++은

매우정적이고 Objective-C는매우동적이라고할수있는데,객체가런타임에생성되지않는다면Objective-C는 다채롭지 않을 것이다.객체를 유지하거나 해제하는 방법에 대한 자세한 설명은 40쪽 6절 절을 참조하자.

5.1 생성자, 초기화5.1.1 할당과 초기화의 차이

C++에서 객체의 할당과 초기화는 생성자를 호출할 때 동시에 수행된다. Objective-C에서 할당과초기화 두 가지는 전혀 다른 메소드이다.할당은 클래스 메소드 alloc이 처리하고, 이때 alloc은 모든 인스턴스 데이터를 초기화한다.

런타임에 새로 만든 객체의 정확한 타입을 설명하는 값을 가진 NSObject의 isa(is-a) 포인터를 제외하고는인스턴스데이터는 0으로설정된다.인스턴스데이터가생성될때파라미터에따라특정값으로 설정된 후, 해당 코드는 인스턴스 메소드 안으로 강제이주된다. 그 메소드 이름은 일반적으로 init으로 시작한다. 그러므로, Objective-C에서 객체의 생성은 할당과 초기화라는 두 단계로명확하게 나뉘어진다. alloc 메세지는 클래스(class)에게 보내지고, init... 메세지는 alloc에의해 새로 생성된 인스턴스 객체(object)에게 보내진다.초기화 단계는 선택할 수 없으며, alloc의 다음에는 항상 init이 따라와야 한다. 초기화

작업에서는 부모 클래스 초기화를 호출하며, 최종적으로 중요한 작업을 실행하는 최상위 부모NSObject의 init 메소드 호출로 끝나야 한다.

C++에서는,생성자의이름은선택할수없다. Objective-C에서는초기화는일반적인메소드와같은것이며, init접두사를관습적으로사용하지만강제사항은아니다.그러나,초기화 메소드의이름은 “init”으로 시작해야 한다는 규칙은 중요한 권장사항이므로 따르는 것이 좋다.

5.1.2 alloc과 init 사용하기

alloc호출은 init에게보내져야만새로운객체를리턴한다. init호출은또한객체를리턴한다.대부분의 경우 이것은 초기화된 객체이다. 때로는 (하나의 인스턴스가 허용되는 객체인)싱글턴(singleton)를 사용할 때, init는 다른 리턴 값으로 대체할 수 있다. 따라서, init의 리턴 값을무시하면 안된다. 보통 alloc과 init는 같은 라인에서 호출된다.

C++

Foo* foo = new Foo;

Objective-CFoo* foo1 = [Foo alloc];[foo1 init]; //나쁜 예:반환되는 객체를 사용할것

Foo* foo2 = [Foo alloc];foo2 = [foo2 init]; //가능함, 그러나 번거롭다

Foo* foo3 = [[Foo alloc] init]; //좋다, 일반적으로 사용함

객체가제대로생성되었는지아닌지를알기위해서, C++은 catching예외나 0에대한테스트(ifnew(nothrow)가 사용된 경우)를 사용한다. Objective-C에서는 nil에 대한 테스트로도 충분한다.

29

Page 30: C++에서 Objective-C까지 번역자료

5.1.3 올바른 초기화의 예

올바른 초기화를 위해서는 다음 규칙을 지켜야 한다.

• 초기화 메소드의 이름은 init으로 시작한다.

• 초기화 메소드는 사용될 객체를 리턴한다.

• 슈퍼클래스의 init 메소드를 일부를 호출하므로 NSObject의 init이 최종적으로 호출된다.

• 초기화는 [super init...]에 의해 리턴된 값을 가진다.

• 자제적으로 만들었거나 상속받았거나 상관없이 발생한 오류는 정확히 처리해야 한다.

다음의 예제는 C++와 Objective-C에서 객체를 인스턴스화시키는 방법이다.

C++

class Point2D{public:Point2D(int x, int y);

private:int x;int y;

};Point2D::Point2D(int anX, int anY) {x = anX; y = anY;}...Point2D p1(3,4);Point2D* p2 = new Point2D(5, 6);

Objective-C@interface Point2D : NSObject{

int x;int y;

}//주목: Objective-C의 id는 void*와 유사함

//(id)는 가장 "일반적인" 객체 타입임

-(id) initWithX:(int)anX andY:(int)anY;@end

@implementation Point2D

-(id) initWithX:(int)anX andY:(int)anY{//부모 클래스의 초기자는 반드시 호출할 것

if (!(self = [super init])) //부모 클래스가 NSObject이면 이를 초기화한다

return nil; //부모객체의 생성이 실패하면, nil을 반환함

//성공할 경우 추가의 초기화 기능을 수행

self->x = anX;self->y = anY;return self; //생성된 객체 자체를 반환함

}@end...Point2D* p1 = [[Point2D alloc] initWithX:3 andY:4];

30

Page 31: C++에서 Objective-C까지 번역자료

5.1.4 self = [super init...]

생성자의 사용시 가장 놀라운 문장은 self = [super init...]이다. 우리는 모든 메소드마다self라는 현재 객체를 나타내는 숨겨진 인수가 있다는 것을 기억해야 한다. 따라서, 그것은 지역변수인데, 왜 우리는 그 값을 변경해야할까? Will Shipley[8]는 흥미로운 문서에서 이것이 쓸모없는 짓이라는 것을 보였다. 이 인자는 거짓으로 판명난 Objective-C의 런타임에 가정한 것으로밝혀졌고, 이에따라 실제로 self를 수정해야만 한다. 이를 설명하는 조언을 환영한다.

[super init]이 현재 객체가 아닌 다른 객체를 반환할 수 있다. 싱글턴 객체가 바로 그런 경우이겠지만, 그것은 [8]의 주장과 반대된다. 단일 객체를 위해 init을 두 번 호출하는 것은 말이안된다. 그리고 이런 점에서 이것의 상위에 있는 개념이 실패한 것으로 드러난다.아무튼, API는 새로 할당한 객체를 다른 객체로 대체할 수 있다. 코어 데이터19는 다음 데이

터베이스의 필드에 연결된 인스턴스 데이터의 특별한 작업을 처리할적에 그 일을 한다. 코코아가제공하는 NSManagedObject 클래스의하위클래스를만들적에이것을대체하는클래스를만드는것은 강제 사항에 속한다.이 같은 경우에, self는 연속해서 두 개의 서로 다른 값을 가진다. 이 값은, 첫번째로 alloc

에 의해 리턴된 값, 두번째로 [super init...]에 의해 반환된 값이다. self 값은 수정하는 것은게시판(공지) 효과가 있는데, 데이터 멤버에 대한 모든 접근은 아래 코드에서 볼 수 있는 것처럼이를 묵시적으로 사용하는 것이다.

@interface B : A{int i;}@end

@implementation B

-(id) init{//이 단계에서 self 값은 alloc에서 반환한 값이다

//A가 서로다른 "self"를 반환하여 치환(대치)을 수행한다고 가정하자

id newSelf = [super init];NSLog(@"%d", i);//self->i 값을 출력

self = newSelf; //"i"를 변경불가능으로 생각할 수 있으나

NSLog(@"%d", i);//self->i 값을 출력하므로 newSelf->i를 출력함

//이전의 값과 반드시 같을 필요는 없다

return self;}

@end

...B* b = [[B alloc] init];

self = [super init]의 간결한 형식은 결국 버그가 생기는 것을 막는 가장 단순한 방식이다. 객체가 “예전” self를 가리킬 수 밖에 없는 상황을 걱정하는 것도 이해가 된다. 예전 객체는반드시 free로 만들어주어야 한다.이를 위한 첫번째 규칙은 간단한다. 예전 self의 처리는 이것을 대치하는 메소드에 의해 수행

되어야한다.앞서살펴본예제의경우 [super init...]이메모리해지(deallocation)를수행해야한다.예를들면,여러분이 NSManagedObject(치환을수행하는코코아클래스)의하위클래스생성을수행하는경우기존의 self에대해서는할필요가없다.반대로, NSManagedObject의개발자는이를 적절하게 처리해야 한다.

19코어 데이터는 애플에서 제공하는 코코아 API로 파일 시스템에 데이터를 유지하기 위한 프레임워크이다. 코어 데이터를 사용하면 프로그램의 데이터를 객체로 다루어 효율적으로 데이터를 저장하고 검색한 후 가져올 수 있다.

31

Page 32: C++에서 Objective-C까지 번역자료

따라서, 치환을 실행하는 클래스를 개발하는 일이 있다면, 여러분은 그것을 초기화하는 동안할당된 객체를 최소화하는 방법을 알아야 한다. 이 문제는 오류 처리와 동일한다. 우리는 생성을실패(잘못된 파라미터, 이용할 수 없는 리소스..)한 경우에 무엇을 해야할까? 이것은 섹션 32쪽5.1.5절에서 답을 얻을 수 있다.

5.1.5 초기화 실패

객체(초기화 사실)를 생성할 경우 다음의 3 가지 다른 곳에서 오류가 발생할 수도 있다.

1. [super init...] 호출 전 : 생성자 파라미터가 유효하지 않은 것으로 간주되는 경우, 초기화를 최대한 빨리 중지시켜야 한다.

2. [super init...] 호출시 : 수퍼클래스의 생성이 안될 경우 우리는 현재 작업을 중단해야한다.

3. [super init...] 호출 후 : 추가적인 리소스 할당에 실패하는 경우.

모든 경우에 대하여 생성자는 nil을 리턴해야 하며 현재 오류를 발생시킨 객체의 메모리 해제(deallocating)를 수행해야 한다. 위의 경우를 살펴보면 우리는 2의 경우가 아닌 1과 3의 경우에대해처리를해야한다.현재객체의할당을취소하려면우리는단지 [self release]를호출하면된다(비고. 메모리 관리에 관해서는 release에 관해 설명하는 40쪽 6절 참고).객체의 해제는 (비고. 35쪽 5.2절의 소멸자 참고)을 호출하면 된다. 이에따라, dealloc 메소

드의 구현은 부분적으로 초기화된 객체와 맞아야 한다. 모든 인스턴스 데이터가 alloc에 의해 0으로 초기화 된다는 사실은 이런 점에서 상당히 유용하다.

@interface A : NSObject {unsigned int n;

}-(id) initWithN:(unsigned int)value;@end

@implementation A

-(id) initWithN:(unsigned int)value{//사례 #1 (생성자가 적절한가?)if (value == 0) //여기서 우리는 양의 값만을 원한다고 가정한다

{[self release];return nil;

}

//사례 #2 (슈퍼 클래스가 성공적으로 객체를 만드는가?)if (!(self = [super init])) //self로 대치되기는 했지만 이 객체는 부모 객체임

return nil; //에러가 나면 "self"를 해제할 책임은 누가 져야할까?

//사례 #3 (초기화가 이루어질까?)n = (int)log(value);void* p = malloc(n); //자원을 할당하려 함

if (!p) //실패할 경우 우리는 에러로 처리함

{[self release];return nil;

}}

@end

32

Page 33: C++에서 Objective-C까지 번역자료

5.1.6 alloc+init으로 생성자를 “쪼개기”

alloc와 init을계속해서연속적으로사용하는것은프로그램개발시번거로울수도있다.다행스럽게도, 이 번거로움은 편리한 생성자(convenice constructor)로 단축할 수 있다. 이러한 생성자의정확한 이해를 위해서는 Objective-C의 메모리 관리의 개념을 잘 알아야 한다. 이 문서의 43쪽6.4.6절에 보다 더 자세한 설명이 나와있다. 간단히 설명하면 편리한 생성자는 클래스의 이름으로접두사가 붙어있는데, init 메소드처럼 실행하지만 내부적으로 alloc까지 수행하는 특징이 있다.그러나이객체는오토릴리즈풀(autorelease pool)(비고. 41쪽 6.4절)에함께등록되어 retain메세지가 전달되지 않은 경우에는 제한된 라이프 사이클을 갖는다. 아래에 그 예가 있다.

//번거로운 방법

NSNumber* tmp1 = [[NSNumber alloc] initWithFloat:0.0f];...[tmp1 release];

//쉬운방법NSNumber* tmp2 = [NSNumber numberWithFloat:0.0f];...//release가 필요없다

5.1.7 디폴트 생성자 : 초기화 지정

Objective-C에는 디폴트 생성자 개념이 없다. 모든 객체가 동적으로 할당되기 때문에, 객체가 생성될 때 갖는 속성은 항상 외부적으로 주어져야 한다. 그러나, 기본 생성자는 몇 가지 코드를인수화하는 데 사용할 수 있다. 실제의 경우, 정확한 초기화는 대부분 다음과 비슷할 것이다.

if (!(self = [super init])) //"init" 혹은 부모 클래스의

return nil; //적절한 초기화 메소드를 호출함

//객체 생성이 성공할 경우 적절한 코드 추가

return self;

코드 중복은 아주 나쁜 코딩방식이기 때문에 각각의 초기화단계에서 이 구조는 매번 반복

된다. 가장 좋은 해결책은 이 코드에서 가장 중요한 초기화 메소드를 잘 만드는 것이다. 그 후다른초기화는지정된 초기자로알려진이 “잘만든”초기화를호출한다.논리적으로, Objective-C에서는 파라미터에 디폴트 값을 주는 것이 불가능하기 때문에 지정된 초기화는 가장 많은 수의

파라미터를 가지게 될것이다.

33

Page 34: C++에서 Objective-C까지 번역자료

-(id) initWithX:(int)x{return [self initWithX:x andY:0 andZ:0];

}

-(id) initWithX:(int)x andY:(int)y{return [self initWithX:x andY:y andZ:0];

}

//지정된 초기자

-(id) initWithX:(int)x andY:(int)y andZ:(int)z{if (!(self = [super init]))return nil;

self->x = x;self->y = y;self->z = z;return self;

}

지정된 초기자가 가장 많은 파라미터를 가진 것이 아닌 경우는 그다지 유용하지 않다.

//아래 코드는 번거롭다

-(id) initWithX:(int)x //지정된 초기자

{if (!(self = [super init]))return nil;

self->x = x;return self;

}-(id) initWithX:(int)x andY:(int)y{if (![self initWithX:x])return nil;

self->y = y;return self;

}-(id) initWithX:(int)x andY:(int)y andZ:(int)z{if (![self initWithX:x])return nil;

self->y = y;self->z = z;return self;

}

34

Page 35: C++에서 Objective-C까지 번역자료

5.1.8 초기화 리스트와 인스턴스 데이터의 디폴드 값

C++생성자의 초기화 리스트 기법은 Objective-C에 존재하지 않는다. 그러나, C++의 방식과는

달리 Objective-C의 alloc 메소드가 모든 인스턴스의 데이터를 0으로 초기화하여 포인터가 nil로 설정되는 것은 주목할만하다. 이 방식은 객체를 속성으로 사용하는 C++에서는 문제가될 수

있으나, Objective-C에서는 객체가 항상 포인터로 표현된다.

5.1.9 가상 생성자

진짜 가상 생성자를 얻는 것은 Objective-C에서 가능하다. 보다 자세한 내용은 메모리 관리(40쪽6절) 내용을 본 후 43쪽 6.4.6절을 보자.

5.1.10 클래스 생성자

Objective-C에서는, 클래스 자체가 객체이기 때문에, 재정의될 수 있는 생성자를 제공한다. 그것은 NSObject에서 상속된 클래스 메소드이며, 그 프로토타입은 +(void) initialize; 이다.이 메소드는 클래스 또는 그 하위 클래스 중 하나를 사용하는 경우 자동으로 호출된다. 그러나 이 메소드는 주어진 클래스를 위해 한번만 호출된다는 것은 사실이 아니다. 사실 하위 클래스는 +(void) initialize를 재정의하지 않으면 Objective-C 메카니즘은 상위 클래스로 부터+(void) initialize를 호출한다.

5.2 소멸자

C++에서 소멸자(destructor)는 생성자처럼 재정의될 수 있는 구체적인 메소드이다. Objective-C에서 소멸자는 dealloc이라는 이름을 가지는 인스턴스 메소드이다.

C++에서 소멸자는 객체를 해제될 때 자동적으로 호출되는데, 그것은 Objective-C에서도 마찬가지이며, 객체를 해제하는 방법만 다르다(비고. 40쪽 6절). 주의할 점은 Objective-C에서는소멸자를 명시적으로 호출하면 안된다는 점이다. 사실, C++에서 할 수 있는 하나의 방법이 있는

데,개발자스스로할당에사용되는메모리풀을관리하는경우이다.그러나 Objective-C에서어떤경우도 dealloc을 명시적으로 호출해서는 안된다. 우린는 코코아에 있는 사용자 정의 메모리 영역을 사용할 수 있지만 그 사용이 일반적인 할당/해제 시도에는 영향을 미치지 않는다(비고. 36쪽 5.3절).

C++

class Point2D{public:~Point2D();

};

// C++의 소멸자

Point2D::~Point2D() {}

Objective-C@interface Point2D : NSObject-(void) dealloc; //이 메소드는 재정의 가능하다

@end

@implementation Point2D//이 예제에서는 재정의가 필수는 아니다

-(void) dealloc{[super dealloc]; //부모 클래스에게 전달하는 것을 잊지말자

}@end

35

Page 36: C++에서 Objective-C까지 번역자료

5.3 복사 연산자

5.3.1 고전적인 복제방식, copy, copyWithZone:, NSCopyObject()

C++에서복사생산자와영향력(affectation)연산자의일관성있는구현을정의하는것은중요하다.Objective-C에서 연산자 오버로딩이 불가능하며, 복제 메소드(cloning method)가 정확한지만을확인한다.코코아에서 복제는 메소드의 구현을 요청하는 NSCopying이라는 이름의 프로토콜(비고.23쪽

4.4절)과 관계가 있다.

-(id) copyWithZone:(NSZone*)zone;

이 메소드의 매개변수 zone은 할당되어야할 복제품이 있어야할 메모리 영역이다. 코코아는 다른커스텀 영역의 사용을 허용하는데, 몇몇 메소스가 인자로 이 영역을 취한다. 대부분의 경우, 디폴트 영역으로 충분하기 때문에 매번 다른 영역을 지정할 필요는 없다. 다행히 NSObject는 디폴트zone 영역을 매개변수로 copyWithZone:의 메소드를 캡슐화한 다음 메소드를 제공한다.

-(id) copy;

하지만 NSCopying에필요한것은 copyWithZone:버전이다.마지막으로유틸리티함수 NSCopyObject()는약간다른기법을제공하는데,이방법은간단하지만주의가필요한다.우선이코드는 NSCopyObject(...)코드에대한고려를하지않고만든것이다.이부분에대한설명은다음페이지 37쪽 5.3.2절에서설명한다.

Foo 클래스에 대한 copyWithZone: 구현은 다음과 같을 것이다.

//부모 클래스가 copyWithZone를 구현하지 않을 경우

//NSCopyObject()를 사용하지 않음

-(id) copyWithZone:(NSZone*)zone{//새 객체를 반드시 구현해야함

Foo* clone = [[Foo allocWithZone:zone] init];//인스턴스 데이터는 반드시 수동으로 복사함

clone->integer = self->integer; //"integer"는 여기서 "int"형임//clone에게 하위객체를 위한 동일한 메커니즘을 트리거함

clone->objectToClone = [self->objectToClone copyWithZone:zone];//하위객체는 복제되지 않으나 공유될 수 있음

clone->objectToShare = [self->objectToShare retain]; //비고, 메모리 관리

//mutator가 있을 경우, 이는 양쪽 모두 사용될 수 있음.[clone setObject:self->object];return clone;

}

영역(zone) 파라미터를 처리하기 위해, alloc 대신 allocWithZone:을 사용한 것에 주목하기바란다. alloc은 allocWithZone:에서디폴트영역으로항당하는호출메소드를캡슐화한것이다.코코아의 정의 구역 관리에 대한 자세한 내용을 보려면, 코코아 다큐먼트를 참조하자.어쨌든, 우리는 부모클래스에서 copyWithZone: 이 사용가능한지 살펴야만 한다.

36

Page 37: C++에서 Objective-C까지 번역자료

//만일 부모 클래스가 copyWithZone:을 구현하여 NSCopyObject()를//사용하지 않은 경우

-(id) copyWithZone:(NSZone*)zone{Foo* clone = [super copyWithZone:zone]; //새 객체 생성

//여러분은 반드시 현재 하위클래스에 한정된 인스턴스 데이터를

//복제하여야만 한다.clone->integer = self->integer; //"integer"는 여기서 "int"형임//하위 객체를 복제하는 동일한 메커니즘을 트리거함

clone->objectToClone = [self->objectToClone copyWithZone:zone];//하위객체는 복제되지 않으나 공유될 수 있음

clone->objectToShare = [self->objectToShare retain]; //비고, 메모리 관리

//mutator가 있을 경우, 이는 양쪽 모두 사용될 수 있음.[clone setObject:self->object];return clone;

}

5.3.2 NSCopyObject()

NSObject클래스는 NSCopying프로토콜을구현하지않는다.그때문에 [super copy...]는호출하여직접적인자식클래스를만드는혜택을받을수없고, [[... alloc] init]구조를바탕으로표준 초기화방식을 취해야만 한다.단순한코드를만들기위하여유틸리티함수 NSCopyObject()를사용할수있지만포인트멤버

(객체포함)를 가지고 있을 경우 몇 가지 주의가 필요하다. 이 함수는 객체의 이진파일 복사본을생성하는게 그 원형(prototype)은 다음과 같다.

//추가의 바이트는 대게 0이다. 그러나 색인화된 인스턴스 데이터를 위한

//확장된 공간을 위하여 사용할 수 있다.id <NSObject> NSCopyObject(id <NSObject> anObject,

unsigned int extraBytes, NSZone *zone)

이진 복사는 포인터가 아닌 인스턴스 데이터의 복사를 자동화할 수 있다. 하지만 포인터 멤버(객체를포함)를위해서는포인트로가리켜진데이터에대한추가적인참조가드러나지않게생성됨을명심해야한다.이러한작업을위하여평소에정확한복제로부터나오는것값에서포인터를재설정하는 일을 하도록 한다.

//만일 부모 클래스가 copyWithZone:을 구현하지 않은 경우

-(id) copyWithZone:(NSZone*)zone{Foo* clone = NSCopyObject(self, 0, zone);//binary copy of data//clone->integer = self->integer; //불필요함: 이전데이터는 이미 복사됨

//복제를 위한 하위 객체는 반드시 복제되어야 함

clone->objectToClone = [self->objectToClone copyWithZone:zone];//공유를 위한 하위 객체는 반드시 새로운 참조로 등록되어야함

[clone->objectToShare retain]; //비고, 메모리 관리

//치환자는 clone->object를 해제하려한다.//그러나 이 경우 포인터 값의 이진복사로 인해 바람직하지 않다

//따라서 치환자를 사용하기전에 포이터를 리셋한다

clone->object = nil;[clone setObject:self->object];return clone;

}

37

Page 38: C++에서 Objective-C까지 번역자료

//만일 부모클래스가 copyWithZone:을 구현한 경우

-(id) copyWithZone:(NSZone*)zone{Foo* clone = [super copyWithZone:zone];//부모클래스가 NSCopyObject()를 구현하는가? 이것은//나머지 할일을 알아야 하기때문에 중요하다.

clone->integer = self->integer;//NSCopyObject()가 사용된적 없는 경우

//의심나는 경우, 시스템적으로 달성할 수 있다

//복제를 위한 하위 객체는 정말로 복제되어야 한다

clone->objectToClone = [self->objectToClone copyWithZone:zone];

//NSCopyObject() 인지 아닌지 리테인을 해야함 (비고. 메모리 관리)clone->objectToShare = [self->objectToShare retain];

clone->object = nil; //의심나는 경우 리셋하도록 한다

[clone setObject:self->object];

return clone;}

5.3.3 더미 복제, 치환성, mutableCopy 와 mutableCopyWithZone:

변경불가능 객체를 복제할 때, 근본적인 최적화는 실제로 복제하는 대신 복제가되는 것처럼 가정하는 것이다. 이 방법으로 객체의 레퍼런스가 반환된다. 우리는 이 방법을 시작으로 치환불가 및치환가능한 객체의 개념을 구분할 수 있다.치환불가 개체는 인스턴스 데이터에 대한 변경을 가할 수 없다. 오직 초기화를 통해서만 이

객체에 대한 상태값을 줄 수 있다. 이 경우, 그 자체의 참조만을 반환할 수 있으면 “의사-복제(pseudo-cloned)”를 안전하게 할 수 있다. 역시 그것도 복제를 수정할 수 없기 때문에 그들 중 무엇이라도 의도하지않게 상대방이 수정될 경우 이 수정값에 영향을 받지 않는다. 가장 효과적인copyWithZone: 구현은 이 경우에 다음과 같이 제안할 수 있다.

-(id) copyWithZone:(NSZone*)zone{//객체는 자신의 참조 카운터를 증가시킨 후 자신을 반환한다.return [self retain]; //메모리 관리에 관한 절을 볼것

}

retain의 사용은 Objective-C에서 유래한다(비고. 40쪽 6절). 참조 카운터는 그 클론을 삭제할때 원래의 객체가 파괴되지 않도록 하기위하여 개발된 구조이다. 클론이 생성되면 클론의“공식적인” 존재로 만들기 위해 참조 카운터가 1 씩 증가한다.

“의사-복제”는여유가있는최적화가아니다.객체를생성할적에는메모리할당이요구되는데이것은 “긴”프로세스이므로가능한적게하는것이좋다.이것때문에객체를가상의복제본과그반대로 가상화될 수 없는 객체 두 종류로 구분한다. “치환불능” 클래스를 생성한 다음 이 클래스의 데이터 인스턴스를 변경하기 위한 메소드를 추가하기 위해 선택적으로 그들을 “치환가능한”버젼으로 서브클래싱한 클래스를 알아내는것은 쉽다. 예를 들어 코코아에 NSMutableString은NSString의 치환가능한 하위 클래스이고, NSMutableArray는 NSArray의 치환가능한 하위 클래스이며, NSMutableData은 NSData의 치환가능한 하위 클래스이다. 이와 유사한 클래스는 더 있을수 있다.하지만 앞서 설명한 기법을 통해 스스로 “가짜-복제”만하는 치환불능 객체를 통해 안전하게

변형복제본을얻는것이불가능해보인다.이러한제한은 “외부세계”로부터그들을분리시키므로분리 불변 객체의 유용성을 감소시킬 것이다.

38

Page 39: C++에서 Objective-C까지 번역자료

이를 위하여 NSCopying 프로토콜 뿐만아니라, 다음 구현을 요청하는 NSMutableCopying 이라는 또 다른 프로토콜(비고. 23쪽 4.4절)이 있다.

-(id) mutableCopyWithZone:(NSZone*)zone;

mutableCopyWithZone:메소드는변경할수있는복사본을리턴해야하며,본래객체에는수정된내용이적용되지않는다. copy메소드와유사하게주어진디폴트영역으로 mutableCopyWithZone:을 자동적으로 호출하는 mutableCopy 메소드가 있다. mutableCopyWithZone:의 구현은 다음과같이, 이것은 이전에 제시한 고전적인 복사와 비슷하다.

//만일 부모클래스가 mutableCopyWithZone:을 구현하지 않은 경우

-(id) mutableCopyWithZone:(NSZone*)zone{Foo* clone = [[Foo allocWithZone:zone] init]; //또는 가능한 경우 NSCopyObject()clone->integer = self->integer;//copyWithZone:처럼, 일부 하위 객체는 복제가능하며 일부는 공유가능하다

//mutableCopyWithZone:을 호출하여 치환가능한 하위 객체를 복제할 수 있다

//...return clone;

}

슈퍼클래스의 가능한 mutableCopyWithZone:를 사용하는 것을 잊지말자.

//만일 부모클래스가 mutableCopyWithZone:을 구현한 경우

-(id) mutableCopyWithZone:(NSZone*)zone{Foo* clone = [super mutableCopyWithZone:zone];//...return clone;

}

39

Page 40: C++에서 Objective-C까지 번역자료

6 메모리 관리

6.1 new 와 delete

C++ 키워드인 new와 delete는 Objective-C에서는 지원하지 않는다. (Objective-C에서 new는 메소드 형태로 존재한다. 하지만 단지 alloc+init를 위한 단축 메소드일 뿐이며, 그다지 권장하는방식은 아니다. C++의 두 키워드는 각각 alloc(비고. 29쪽 5.1절)과 release(비고. 40쪽 6.2절40쪽)를 호출하는것으로 대치한다.

6.2 참조 횟수

Objective-C는동적객체를사용하므로메모리관리는언어의중요부분중하나이다. C또는 C++

에서 메모리 영역은 한번에 할당되고 한번에 삭제된다. 메모리 역역은 원하는만큼 많은 포인터변수가 참조할 수 있지만 오직 한 포인터에서만 delete가 호출된다.또 다른 한편으로 Objective-C는 객체의 참조 횟수 구조표를 구현하고있다. 이 구조를 통해 객

체는몇번이나참조되는지알수있다.이것은개와개목줄의비유로설명될수있다.(MacOS X [7]에서 가져온 비유임) 만약 객체가 개라면 모든 이들은 그것을 묶을 수 있는 개목줄을 이용해서물어볼 수 있다. 만약 아무도 그 개에게 더이상 관심이 없다면 그 개목줄을 놓을 수 있다. 개가적어도 하나의 개목줄에 묶여있다면, 반드시 거기에 머물러 있어야 한다. 하지만 개목줄이 0으로떨어지자마자 개는 자유롭게된다!보다 더 기술적으로 말하자면 새롭게 만들어진 객체의 참조 횟수는 1로 설정된다. 만약 코

드의 한부분이 객체를 참조할 필요가 있다면, 이 코드가 retain 메세지를 보낼수 있다, 이렇게하면 참조 횟수가 1 증가될 것이다. 코드의 한부분이 더이상 객체를 필요로 하지 않을때 release메세지를 보낼 수 있으며, 이때마다 객체의 참조 횟수는 1 감소된다.객체는 참조 횟수가 양의 값인 경우 필요한 만큼의 retain과 release 메세지를 받을 수 있다.

그러나 참조 횟수가 0으로 떨어지자마자 소멸자 dealloc이 자동으로 호출된다. 객체의 주소에release 메세지를 보내는 것은 이제 더이상 효력이 없고 메모리 장애를 유발하는 계기가 될뿐이다.이기법은 C++ STL이가진 auto_ptr기능과동등하지않다.반대로부스트라이브러리(boost

library) [5]는 포인터를 shared_ptr 클래스로 캡슐화시키는 기능을 제공하는데 이 기능이 참조횟수 구조를 구현하고 있다. 하지만 이것은 표준 라이브러리가 아니다.

6.3 alloc, copy, mutableCopy, retain, release메모리 관리를 이해하는 것만으로는 이것을 어떻게 사용하는지 충분히 설명하지 못한다. 이 절의 목표는 몇가지 규칙을 알려주는 것이다. 키워드 autorelease는 지금 이해하긴는 더욱 어렵기때문에 이 절에서 설명하지 않을 것이다.메모리관리에적용되는기본규칙은 alloc, [mutable]copy[WithZone:]또는 retain을이용

하여 참조 횟수를 증가시키는 모든 경우 이에 대응하는 [auto]release를 반드시 사용할 의무가있다는 것이다. alloc, [mutable]copy[WithZone:] 또는 retain을 실제 코딩에서는 객체의 참조횟수를 증가시키는 세 가지 방법이 있다. 이 말이 의미하는 것은 다음의 몇가지 경우에 한하여객체를 반드시 해제시켜야 한다는 것이다.

• alloc으로 객체를 명시적으로 인스턴트화할때

• 명시적으로 copy[WithZone:] 또는 mutableCopy[With Zone:]으로 객체의 복제품(복제품이 실제 복제품이던 의사-복제(pseudo-clone)이든 상관없으며 그다지 중요하지도 않다. 38쪽 5.3.3절)을 만든 경우.

• 명시적으로 retain을 사용한 경우

디폴트로 nil 객체에합당한메세지(release와같은)를보내는것은결과와상관없이충분히가능하다.(20쪽 3.4.1절을 볼 것).

40

Page 41: C++에서 Objective-C까지 번역자료

6.4 자동 해제

6.4.1 autorelease의 중요성

이전절에서언급한규칙은매우중요하니또한번반복해본다. alloc, [mutable]copy[WithZone:]또는 retain을 이용하여 참조 횟수를 증가시키는 모든 경우 이에 상응하는 [auto]release를반드시 사용할 의무가 있다사실단순한 alloc, retain, release만으로이규칙은모두만족시키지 못하는 경우가 있다. 예를 들면 생성자가 아니면서 객체를 생성하도록 설계된 어떤 메소드가있는데, C++(obj3 operator+(obj1, obj2))의 이항 덧셈연자가가 그 좋은 예이다. C++에서 이

연사자가반환한객체는스택에할당되며,그스코프를떠날때자동적으로소멸될것이다.하지만Objective-C에서 그러한 객체는 존재하지 않는다. Objective-C에서 객체를 생성하는 함수는 반드시 alloc을 사용하지만 스택에 그 객체를 반환하기 전에 객체를 해제할 수 없다. 여기에 문제를일으키는 코드에 대한 예제가 있다.

-(Point2D*) add:(Point2D*)p1 and:(Point2D*)p2{Point2D* result = [[Point2D alloc] initWithX:([p1 getX] + [p2 getX])

andY:([p1 getY] + [p2 getY])];return result;

}//오류 : 메소드는 "alloc"을 수행하므로 참조횟수가 1인 객체를 만든다

//규칙에 따라 이 함수는 객체는 소멸시켜야 한다

//이에 따라 다음과 같이 세 점을 더하는 연산은 메모리 누수를 유발함

[calculator add:[calculator add:p1 and:p2] and:p3];

//첫 덧셈의 결과 객체는 참조 포인터가 없으므로

//아무도 해제시킬 수 없다. 이것이 메모리 누수이다.

-(Point2D*) add:(Point2D*)p1 and:(Point2D*)p2{return [[Point2D alloc] initWithX:([p1 getX] + [p2 getX])

andY:([p1 getY] + [p2 getY])];}//오류 : 이 방법 역시 위의 코드와 동일하다.//사용된 중간변수는 마찬가지로 참조하는 (개)목줄이 없다.

-(Point2D*) add:(Point2D*)p1 and:(Point2D*)p2{Point2D* result = [[Point2D alloc] initWithX:([p1 getX] + [p2 getX])

andY:([p1 getY] + [p2 getY])];[result release];return result;

}//오류 : 이 코드는 말이 안되는 코드이다.//객체를 만들자마자 소멸시키고, 또 이미 소멸된 객체를 반환할 수는 없다.

여기서는잘못된것이무엇인지보여주는예시를살펴보았다.이는매우다루기힘든문제처럼보인다. autorelease가없었더라면정말어려운문제일것이다.단순히말하면객체에 autorelease를 전송하는 것은 “나중에” 수행될 release를 객체에게 전송하는 것이다. 하지만 “나중”이 “아무때나”를 의미 하는 것은 아니다. 이는 다음 페이지 42쪽 6.4.2절에서 자세하게 다뤄질 것이다.우선 여기서는 위의 문제에 대한 유일한 해결책을 설명한다.

41

Page 42: C++에서 Objective-C까지 번역자료

-(Point2D*) add:(Point2D*)p1 and:(Point2D*)p2{Point2D* result = [[Point2D alloc] initWithX:([p1 getX] + [p2 getX])

andY:([p1 getY] + [p2 getY])];[result autorelease];return result; //"return [result autorelease]"로 짧게 쓸 수 있다

}//해결책 : "result"는 나중에 호출한 메소드가 사용된 후

//자동적으로 해제된다

6.4.2 오토릴리즈 풀(autorelease pool)

이전 절에서는, autorelease는 적당한 순간에 자동적으로 적용되는 마법같은 release 종류로설명되었다. 하지만 컴파일러가 그 적당한 순간을 예측하지는 못한다. 이 경우, 가비지 수집기가더 유용할 것이다. 그것이 어떻게 작동하는지 설명하기 위해서, autorelease에 관하여 더 상세히 다루는 것이 필요하다. 객체는 autorelease 메시지를 받을 때마다, 매번 “오토릴리즈 풀”에등록된다. 이 풀이 파괴될 때, 객체는 진짜 release 메시지를 받는다. 그렇다면 이 풀은 어떻게다뤄질까? 라는 문제를 살펴봐야 할 것이다.이에 대한 정답은 하나가 아니다. 우선 그래픽 인터페이스로 애플리케이션에 코코아를 사용

한다면 대개 해줄 일은 없다. 대신 풀을 스스로 생성하고 제거해야만 할 것이다.그래픽 인터페이스를 지닌 애플리케이션은 일반적으로 이벤트 루프를 사용한다. 그 루프는

사용자로부터 액션을 기다리다가 그 액션을 수행할 때 프로그램을 깨우고, 다음 이벤트까지 다시휴면상태로 되돌아간다. 코코아로 그래픽 애플리케이션을 생성할 때, 오토릴리즈 풀은 루프의 시작에서 자동적으로 생성되고 끝에서 자동적으로 파괴된다. 이는 논리적이다: 대개 사용자 액션은단계적인 태스크를 촉발한다. 임시 객체가 생성되고, 파괴된다. 다음 이벤트를 위해 유지할 필요가 없기 때문이다. 임시객체 중 어떤 것이 고정되어야 한다면, 개발자는 필요에 따라 retain을사용해야만 한다.반면, 그래픽 인터페이스가 없을 때는 필요하다면 오토릴리즈 풀을 생성해야만 한다. 객체가

autorelease 메시지를 수신하면, 이 객체는 가장 근접한 오토릴리즈 풀을 발견하는 법을 스스로 안다. 풀을 비워야할 시간이되면 단순한 release만으로 파괴할 수 있다. 대개의 커맨드 라인코코아 프로그램은 다음 코드를 갖는다.

int main(int argc, char* argv[]){NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];//...[pool release];return 0;

}

MacOS X 10.5부 NSAutoreleasePool 클래스에 drain 메소드를 추가한 것에 주목하자. 이 메소드는가비지수집기가활성화된경우 release메소드와동일하며,가비지수집기를수행시킨다(비교, 49쪽 6.6절). 이 방법은 모든 경우에서 동일하게 동작하는 코드를 만드는데 유용하다.

6.4.3 다수의 오토릴리즈 풀 사용하기

프로그램은하나이상의오토릴리즈풀을가지는것이가능하며,이방식이때론유용하다. autorelease를 수신하는 객체는 가장 근접한 풀에 등록할 것이다. 따라서, 함수가 많은 임시 객체를 생성하고사용하는 경우에, 향상된 성능은 지역 오토릴리즈 풀을 생성하여 얻을 수 있다. 이 방법으로인해임시 객체의 그룹은 가능한 빨리 파괴되며, 함수가 리턴된 후 메모리를 절약할 수 있을 것이다.

6.4.4 autorelease에서의 주의사항

autorelease는 편리하기 때문에 잘못 사용될 수도 있다.

• 먼저,필요이상의 autorelease호출을보내는것은너무많은 release호출을보내는것과유사한다. 이 경우 풀을 비울 때 메모리 오류를 발생시킨다.

42

Page 43: C++에서 Objective-C까지 번역자료

• 어떤 release메시지든지 autorelease로대체될수있다는것이사실이라하더라도, autorelease는 성능면에서 release보다 못하다. 이것은 오토릴리즈 풀이 정상 release보다 더 많은 작업을하기때문이다.더욱이,모든메모리해지를지연시키는것은불필요하게메모리낭비를초래할 수 있다.

6.4.5 autorelease와 retain

autorelease덕에,메소드는스스로해제될수있는객체를만들수있다.그러나,객체가더오래유지하려하는것이일반적이다.이런경우에,객체가자기에게 retain메시지를전송하여메모리해제를 위한 계획을 수립할 수 있다. 다음은 객체의 수명에 관한 두 가지 관점이다.

• 함수의 개발자 관점에서, 객체가 만들어지고 그것의 해제되는 계획이 수립될 수 있다.

• 함수 호출의 입장에서 그 객체의 수명은 retain으로 증가시킬 수 있다(autorelease를 이용하여 참조 횟수를 0으로 감소시키지 않은 함수에 의한 계획). 그러나 호출 함수는 참조횟수가 1 증가된 객체를 나중에 해지할 의무까지 동시에 가진다.

6.4.6 편리한 생성자, 가상 생성자

alloc과 init의 연속적인 사용으로 인해 애플리케이션 코드작성은 번거로울 수 있다. 다행히,이 수고스러움은 편리한 생성자(convenience constructor)의 개념으로 해결할 수 있다. 이 생성자는, 해당 클래스의 이름이 접두사로 사용되어야하며, init 메소드처럼 작동하지만, 자체적으로객체 할당 alloc을 수행한다. 이때 리턴된 객체는 오토릴리즈 풀에 등록되며, retain을 보내지않는다면 일시적으로 유지된다. 다음의 예를 통해 살펴보자.

//번거롭다NSNumber* zero_a = [[NSNumber alloc] initWithFloat:0.0f];...[zero_a release];

...

//간편하다NSNumber* zero_b = [NSNumber numberWithFloat:0.0f];...//해제(release)가 필요없다

메모리관리에대한절(Section 40쪽 6절)을살펴보면,이러한생성자가 autorelease를사용하는것은 분명해 보인다. 이 메소드의 기저에 있는 코드는 self의 정확한 사용을 필요로하기 때문에그다지 명확하지는 않다. 실제로, 편리한 생성자는 self가 meta-class 인스턴스인 Class 타입의 객체를 참조하도록 해주는 클래스 메소드(+ 타입을 사용함)이다. 인스턴스 메소드인 초기자(initializer)에서 self는 “정상적인” 객체를 참조를 의미하는 클래스의 인스턴스이다.나쁜 편리한 생성자를 쓰는 것은 어렵지않다. Vehicle 클래스의 색상을 지정하는 편리한 생

성자를 제공한다고 가정해 보자.

//Vehicle 클래스@interface Vehicle : NSObject{NSColor* color;

}-(void) setColor:(NSColor*)color;

//편리한 생성자

+(id) vehicleWithColor:(NSColor*)color;@end

편리한 생성자의 구현은 다소 미묘한 문제가 있을 수 있다.

43

Page 44: C++에서 Objective-C까지 번역자료

//나쁜 편리한 생성자

+(Vehicle*) vehicleWithColor:(NSColor*)color{//"self" 값은 여기서 바뀌면 안된다

self = [[self alloc] init]; // 오류 ![self setColor:color];return [self autorelease];

}

이 프로그램에서는 클래스 메소드가 현재 클래스의인스턴스인 self를 참조한다. 그러나 존재하지 않는 인스턴스가 인스턴스를 설정하기 때문에 있을 수 없는 일이다.

//거의 완벽한 생성자

+(id) vehicleWithColor:(NSColor*)color{id newInstance = [[Vehicle alloc] init]; // 성공이다. 그러나 잠재적인

// 하위 클래스 문제를 안고 있다

[newInstance setColor:color];return [newInstance autorelease];

}

우리는 여기서 이것을 개선할 수 있다. Objective-C에서는 가상 생성자의 행동을 가져올 수 있다.생성자는 메소드를 실행하는 객체의 실제 클래스가 어느 것인지 알기 위해 사전조사를 실행해야

한다. 그러면 정당한 하위 클래스를 생성할 수 있다. 이를 위하여 가짜 키워드 class를 사용할 수있다. 이 가짜 키워드는 현재 객체의 클래스 객체(메타 클래스 인스턴스)를 리턴하는 NSObject의메소드이다.

@implementation Vehicle+(id) vehicleWithColor:(NSColor*)color{id newInstance = [[[self class] alloc] init]; // 완벽하다. 클래스는

// 동적으로 식별된다.[newInstance setColor:color];return [newInstance autorelease];

}@end

@interface Car : Vehicle {...}@end

...

//(붉은색상) car 객체를 생성

id car = [Car vehicleWithColor:[NSColor redColor]];

초기화를 위한 init 접두사를 붙이는 규칙과 유사하게 편리한 생성자의 접두사로 클래스의이름을붙이는것이좋다.앞서살펴본코드에서는규칙에따라 [NSColor colorRed]를사용하지않고 [NSColor redColor]로작성했는데,이와같이예외적인규칙이적용된경우는아주드물다.마지막으로, 규칙을 또한번 반복해보자 : alloc, [mutable]copy[WithZone:] 또는 retain을

이용하여 참조 횟수를 증가시키는 모든 경우 이에 상응하는 [auto]release를 반드시 사용할의무가 있다 편리한 생성자를 호출(calling)할 경우, 명시적으로 alloc을 호출하지 않기 때문에release를 책임지지 않는다(해제 또는 소멸에 대한 의무는 없다). 그러나, 이러한 생성자를 만들(creating)경우, alloc을 사용했으므로 autorelease를 잊어서는 안된다.

44

Page 45: C++에서 Objective-C까지 번역자료

6.4.7 설정자

설정자(setter)(또는변형접근자)는 Objective-C의메모리관리에대한지식없이사용하기가어려운전형적인예이다. title이라는 NSString 클래스로캡슐화된객체를가정하여,이문자열객체의값을 변경한다고 가정해 보자. 이것은 아주 간단한 예제인 설정자(setters)에 관한 주요 문제를 부각시킨다. 즉, 매개변수를 어떻게 사용할 것인가?라는 문제이다. C++와는 달리 Objective-C에는하나의 프로토타입만이 적법하기 때문에(비록 객체는 포인터를 통해 사용할 수 있으나), 몇개의 구현부를 찾을 수 있다. 이 구현부는 할당(assignation), 리테인을 가진 할당(assignation withretain)또는 copy가될수있다.이들각각은개발자가선택한데이터모델에따라구체적인의미가약간 다른다. 이러한 각각의 경우, 기존에 참조되는 리소스가 있을 경우 메모리 누출을 방지하기위하여 이것을 먼저 해제해야 한다.

단순 할당 (불완전한 코드)외부 객체는 유지(retain)되지 않으며 단지 참조만 되는 경우이다. 이 경우 외부 객체가 수정될 때현재 클래스에서는 이를 알 수 없다. 만일 외부 객체가 참조되기 전에 nil로 설정하지 않고 해지(deallocated)를 시킨다면, 이 참조는 잘못된 참조가 될 것이다.

-(void) setString:(NSString*)newString{

... 메모리 관리는 나중에 자세히 다룬다

self->string = newString; //단순 할당

}

리테인을 가진 할당 (불완전한 코드)retain키워드덕분에외부객체는참조될때마다참조카운터가 1씩증가한다.그러나이경우도외부 객체가 수정된 경우에 이것을 현재 클래스에서 알 수 있다. 또한 외부 객체는 현재 참조가해제되지 않은 한 해지할 수 없다.

-(void) setString:(NSString*)newString{

... 메모리 관리는 나중에 자세히 다룬다

self-> string = [newString retain]; //리테인을 가진 할당

}

복사 (불완전한 코드)복사의 경우 외부 객체는 참조되지 않으며, 복제된 객체가 대신 만들어진다. 따라서 원본 객체가수정될 경우, 이것은 복제된 객체에 반영되지 않는다. 논리적으로, 복제품은 현재 소유자 객체에의해 처리되기 때문에 소요한 객체보다 오래 유지될 수 없다.

-(void) setString:(NSString*)newString{

... 메모리 관리는 나중에 자세히 다룬다

self->string = [newString copy]; //복제;//NSCopying 프로토콜이 사용된다

}

코드를 완료하려면 객체의 이전 상태를 고려해야 한다. 각각의 경우에서, 치환자(mutator)는새로운 것을 설정하기 전에 기존 참조(이것이 있는 경우)를 해제해야 한다. 코드에서 이 부분은까다롭다.

45

Page 46: C++에서 Objective-C까지 번역자료

할당 (불완전한 코드)가장 간단한 경우로, 이전의 참조값을 덮어쓰는 방법이 있다.

-(void) setString:(NSString*)newString{//강한 연결이 없으며, 이전의 참조는 덮어쓰기가 된다

self->string = newString; //할당

}

리테인을 가진 할당 (불완전한 코드)기존 참조가 새로운 참조와 동일하지 않는 경우 기존 참조 값은 해지되어야만 한다.

//나쁜 코드

-(void) setString:(NSString*)newString{self->string = [newString retain];//오류: 메모리 누수발생, 예전의 "string"은 더 이상 참조되지 않음

}

-(void) setString:(NSString*)newString{[self->string release];self->string = [newString retain];//만일 newString == string이고(있을 수 있는 경우임)//newString의 참조 횟수가 1이면 오류

//[self->string release]후에 newString (string)을 사용하는 것은

//유효하지 않음. 이 포인터에서 해지되었기 때문임

}

-(void) setString:(NSString*)newString{if (self->string != newString)[self->string release]; //성공:비록 string이 nil일지라도 해지 메지시는 가능

self->string = [newString retain]; //오류: "if"문 내에 있어야 함

//만일 string == newString인 경우

//참조횟수가 증가하면 안된다

}

46

Page 47: C++에서 Objective-C까지 번역자료

//올바른 코드

//C++ 개발자를 위한 가장 직관적인 격언인

//"변경하기 전에 검사하라"를 연습하자

-(void) setString:(NSString*)newString{//아무일도 하지않는 최악의 경우를 피하자

if (self->string != newString){[self->string release]; //이전 객체를 해지

self->string = [newString retain]; //새 객체를 리테인

}}

//"이전 값을 자동해제"시키는 연습

-(void) setString:(NSString*)newString{[self->string autorelease]; //string == newString일지라도

//release가 autorelease에 의해 지연되므로

//올바르게 동작한다

self->string = [newString retain];//... 따라서 이 리테인이 먼저 발생한다

}

//"리테인 후 해제"시키는 연습

-(void) setString:(NSString*)newString{[self->newString retain]; //참조 횟수가 1 증가한다 (nil일때 예외상황)[self->string release]; //...따라서 여기서는 0이 될 수 없다

self->string = newString; //그러나 여기서는 "리테인"되지 않는다

}

copy (완전한 코드)전형적인 오류 또는 좋은 솔루션에 관해서, 이 케이스는 copy에 의해 retain되는 대체된 할당과거의 동일하다.

의사 복제(pseudo-clonage)복사본은 결과와 상관없이 (비고. 38쪽 5.3.3절) “dummy-cloning” 될 수 있다는 점에 유의하자.

6.4.8 접근자(Getters)

Objective-C언어는모든객체를동적으로할당한다.이객체들은포인터형태로캡슐화되며참조된다.일반적으로,접근자(getter)는포인터값만을리턴하며객체를바로복사하지않는다.접근자메소드의 이름은 일반적으로 데이터 멤버와 동일하기 때문에 Objective-C에서는 이름 충돌을 일으키지않는다.부울형값의경우,술어형식을빌어서읽히도록하기위해 is( 인가?)로접근자를시작할 수도 있다.

47

Page 48: C++에서 Objective-C까지 번역자료

@interface Button{NSString* label;BOOL pressed;

}-(NSString*) label;-(void) setLabel:(NSString*)newLabel;-(BOOL) isPressed;@end

@implementation Button-(NSString*) label //접근자 메소드와 속성명이 동일함

{return label;

}

-(BOOL) isPressed //부울형은 is를 접두어로하여 읽기 쉽게함

{return pressed;

}

-(void) setLabel:(NSString*)newLabel {...}@end

인스턴스 데이터의 포인터를 반환할경우, 이 포인터를 변경할 수 있다면 쉽게 수정할 수 있다.데이터 캡슐화를 준수하는 것이 바람직하므로, 외부 객체가 이 값을 수정하도록 허용하는 것이올바르지 않다.가려진 데이터를 제한하지 않아야 하기 때문에, 외부 객체에 그러한 수정을 허용하는 것은

바람직하지 않을 수 있다.

48

Page 49: C++에서 Objective-C까지 번역자료

@interface Button{NSMutableString* label;

}-(NSString*) label;@end

@implementation Button-(NSString*) label{return label; //가능함. 그러나 내부정보를 잘 아는 사용자는

//리턴 결과를 NSMutableString으로 다운 형변환한 후 이 문자열을

//수정할 수 있다

}

-(NSString*) label{//해결책 1 :return [NSString stringWithString:label];//좋음 : 새로운 변경불가능한 문자열이 반환된다

//solution 2 :return [[label copy] autorelease];//좋음 : NSString 객체의 NSMutableString 복사 (mutableCopy가 아님)//객체가 반환됨

}@end

6.5 리테인 순환(retain cycles)리테인 순환은 피해야 한다. 만일 객체 A가 객체 B를 리테인하고, B와 C가 상호 리테인하여아래와 같은 리테인 순환구조를 가진다고 가정해보자.

A→ B � CA가 B를 해제(release)하려 하더라도, B는 여전히 C에 의해 리테인되기 때문에 해지되지 않을것이다. 그리고 C 또한 B에 의해 유지되기 때문에 해지될 수 없다. 이 경우 그러나, A는 그 순환구조에 있는 유일한 참조이기 때문에 이 객체에 도달할 수 없다. 이러한 리테인 사이클은 대개메모리 누출로 이어진다. 예를들면 트리 구조에서 노드가 보통은 그 자식을 유지하지만, 자식은보통 부모를 유지하지 않는다.

6.6 가비지 수집기

Objective-C 2.0(비고. 6쪽 1.2절)은 가비지 수집기를 제공한다. 즉, 더 이상 retain하고 release하지 않아도 모든 메모리 관리를 가비지 수집기가 대행한다. Objective-C의 좋은 선택은 가비지수집기의 선택적 특성할 수 있다는 것이다. 여러분이 정확하게 객체의 수명을 제어할지, 혹은 버그가더적게발생하는코드를자동으로만들고싶은지를개발자가스스로결정할수있다.가비지수집기는 전체적인 프로그램을 위해 활성화되거나 비활성화될 수 있다.가비지수집기가활성화되어있으면, retain, release및 autorelease는아무것도하지않도록

재정의된다. 따라서, 가비지 수집기를 고려하지 않고 작성된 코드는 이론적으로 가비지 수집기에의해 쉽게 재컴파일될 수 있다. 여기서 “이론적”이라는 것은 많은 중요한 세부 사항에서 리소스해방에 대해 민감한 부분이 존재한다는 것을 의미한다. 그러므로, 두 경우 전부를 위해 통일된코드를 작성하기란 쉽지않다. 이 때문에 개발자는 연습을 많이 할 것을 권하고 싶다. 그 자세한세부사항은 애플사의 문서 [2]에 상세히 나와 있다.

MacOS X 과는 달리 iOS는 모바일 앱을 위한 운영제제이기 때문에 메모리 관리를 가비지수집기에 의존하지 않는다. 가비지 수집기를 사용한 프로그램의 경우 메모리 관리를 개발자가

49

Page 50: C++에서 Objective-C까지 번역자료

직접하지 않기때문에 메모리의 활용을 최적화시킬 수 없으며, 이로인해 리소스의 낭비가 심해질가능성이 높다. 따라서 iOS 개발을 위해서는 Objective-C의 메모리 참조 방법에 익숙해져야한다.다음 4개의 절은 제대로 공부를 해야 하는 가장 중요한 포인트를 강조하기 위해 몇몇 어려운

문제 중 일부를 다루고있다.

6.6.1 finalize

가비지 수집기 환경에서 객체는 dealloc을 사용하여 소멸시키는데 이때 객체소멸의 비결정적인(non-deterministic) 순서는 이것과 잘 맞지않는다. finalize 메소드는 리소스의 해제와 효과적인객체 소멸(deallocation)의 두 가지 단계로 나누기 위하여 NSObject에 추가되었다. 그러나 “좋은”finalize 메소드는 세밀하고 보다 지능적이다. 일부 개념의 제한은 문서 [2]를 살펴 보도록 하라.

6.6.2 weak, strong

__weak와 __strong을 선언할 적에 직접 사용하는 경우는 거의 없다. 그렇지만, 이 두 개념에대한 이해는 가비지 수집기와 관련있는 새로운 어려운 개념을 이해하는데 도움이 된다.디폴트로, 객체에 대한 포인트는 __strong 속성을 사용하는 데, 이것은 강한(strong) 참조를

한다는 의미이다. 이것이 의미하는 것은 객체는 참조가 살아있는 한 파괴될 수 없다는 의미이다.이로인한예상되는행동은다음과같다 :모든 (strong)참조객체가소멸되었을때객체는수집되며 메모리에서 해제된다. 어떤 경우, 이것은 그 행동을 무효화시키는데 유용하다. 일부 수집기는소멸이 예정된 이러한 객체를 방지하므로 수집기가 채집한 객체의 수명을 증가시켜서는 안된다.이 경우, 가비지 수집기는 __weak 키워드를 가지고서 weak 참조를 사용한다. NSHashTable이이에 관한 좋은 예제이다(비고. 56쪽 11.1절).참조중인 객체가 사라지면 __weak 참조는 자동적으로 null로 된다(nil로 설정됨).이에관한적절한예제는코코아의 Notification Center인데,이것은순수한 Objective-C의영역

바깥에 있으며 이 문서에서는 상세하게 다루지않는다.

6.6.3 NSMakeCollectable()

코코아만이MacOS X의유일한 API는아니다. Core Foundation은또다른 API인데,이것은객체와데이터를공유할수도있고호환도된다.그러나 Core Foundation은 C로만만들어진절차적인API이다. 직관적으로 보기에 가비지 수집기는 Core Foundation 포인터와 함께 작업할 수 없을것 같다. 이 문제는 이미 언급되었으며 가비지 수집기 환경에서도 Core Foundation을 메모리누수없이 사용할 수 있다. NSMakeCollectable 문서는 이 트릭을 이해하는 좋은 시작 문서이다.

6.6.4 AutoZone

애플이 만든 Objective-C 가비지 수집기는 AutoZone이라고 한다. 이것은 공개 소스로 배포되어있다 [1]. 이 버전은 MacOS X10.6(Snow Leopard)에서 큰 발전이 있을것으로 예상된다.

50

Page 51: C++에서 Objective-C까지 번역자료

7 예외 처리

Objective-C에서 예외 처리는 @finally 키워드 때문에 C++보다 Java에 가깝다. finally는 자바에서는 널리 알려져있으나 C++에는 그다지 알려지지 않았다. finally는 try()...catch() 블럭의 추가적인(그러나 선택가능한) 부분으로 @try 구문에서 사용하지 않을 수도 있다. 이와 달리@finally는예외상황이발생하든하지않든,항상실행되어야할코드를포함하고있다.이부분은리소스를 적절하게 해지하기 위한 짧고 깔끔한 코드를 작성하는데 유용하다.이것과는 별도로 Objective-C에서 @try...@catch...의 행동은 매우 고전적인데, C++와는 달

리 오직 객체만이 throw될 수 있다. 다음은 @finally가 있는 것과 없는 구문의 간단한 예제이다.

finally 없는 구문 finally를 가진 구문

BOOL problem = YES;@try{dangerousAction();problem = NO;

}@catch (MyException* e){doSomething();cleanup();

}@catch (NSException* e){doSomethingElse();cleanup();//여기에서 예외가 다시 던져진다

@throw}if (!problem)cleanup();

@try{dangerousAction();

}@catch (MyException* e){doSomething();

}@catch (NSException* e){doSomethingElse();@throw //여기서 예외가 다시 던져진다

}@finally{cleanup();

}

엄밀히 말하면, @finally는 필수는 아니지만, 예외를 제대로 처리하기 위한 유용한 기법이다.앞의예제에서살펴본것처럼 @catch가다시예외를발생시킨경우도잘처리한다.사실, @finally는 @try 범위를 벗어나면 실행되는데, 다음에 이에 관한 설명이 이어진다.

51

Page 52: C++에서 Objective-C까지 번역자료

int f(void){printf("f: 1-you see me\n");//"@"구문을 이해하기 위하여 문자열에 관한 절을 참조하라

@throw [NSException exceptionWithName:@"panic"reason:@"you don’t really want to known"userInfo:nil];

printf("f: 2-you never see me\n");}

int g(void){printf("g: 1-you see me\n");@try {f();printf("g: 2-you do not see me (in this example)\n");

}@catch(NSException* e) {printf("g: 3-you see me\n");@throw;printf("g: 4-you never see me\n");

}@finally {printf("g: 5-you see me\n");

}printf("g: 6-you do not see me (in this example)\n");

}

마지막 포인트 : C++에서 어떠한 것이든 받아들이는 catch(...)키워드는 Objective-C에 존재하지 않는다. 사실, Objective-C에서는 객체만 throw될 수 있기 때문에, id (비고. 10쪽 3.1절)형으로 그것들을 캐치하는 것은 항상 가능한다.코코아는 NSException클래스가있기때문에,여러분이 throw하려고하는어떤객체의부모클

래스로이것을사용하는것이좋다는것을알고잘활용하기바란다.따라서, catch(NSException* e)는 catch(...)와 같은 것으로 보아야한다.

52

Page 53: C++에서 Objective-C까지 번역자료

8 다중 쓰레딩

8.1 쓰레드-세이프티다중 쓰레딩을 실현하기 위해 POSIX20 API를 사용하여 Objective-C에서 프로그램을 작성하는것은 명백하며 당연한 일이다. 코코아는 병렬 쓰레드를 관리하기 위한 자체의 클래스들을 제공한다. 여기서 주의 사항은 간단하다. 하나는 코드를 여러 쓰레드가 동시에 접근할 때 동일한 메모리공간에 접근한 데이터의 동작으로 인하여 예기치 않은 결과가 나타난다는 점이다.

POSIX API는코코아뿐만아니라,락(lock)과뮤텍스(mutex)21객체를실행하고있다. Objective-C는 키워드 @synchronized를 Java 키워드와 동일하게 명명해 제공하고 있다.

8.2 @synchronized

@synchronized(...) 섹션내에 둘러싸인 코드 블록은 한번에 오직 한 스레드에 의해 사용되도록하기 위해 자동적으로 동시접근이 차단된다(lock). 이 방법이 병렬성을 처리하는 가장 좋은해결책이 아니지만, 임계 구역(critical section)에 대한 동시 접근을 차단하는 간단하고 단순한방법으로는 쓸모가 있다.

@synchonized는 자물쇠로 사용되는 매개변수(어떤 객체라도 될 수 있는데, 예를 들면 다음예제와 같이 self도 될 수 있다)로 객체를 필요로 한다.

@implementation MyClass

-(void) criticalMethod:(id) anObject{@synchronized(self){//코드의 이 부분은 (동일한 "self"를 가지는)@synchronized(self)가//있는 모든 블록에 대해 동시접근을 허용하지 않는다

}@synchronized(anObject){//코드의 이 부분은 (동일한 "anObject"를 가지는)@synchronized(anObject)가//있는 모든 블록에 대해 동시접근을 허용하지 않는다

}}

@end

20POSIX는 Portable Operating System Interface [for Unix]의 약자로 서로 다른 UNIX 운영체지의 공통 API를 정리하여 이식성이 높은 유닉스 응용 프로그램을 개발하기 위한 목적으로 IEEE가 정한 애플리케이션 인터페이스 사양이다.이사양에는 C언어인터페이스인시스템호출과프로세스환경,파일과디렉토리,시스템데이터베이스등다양한분야를포함하고 있다.

21뮤텍스(mutual exclusion, Mutex)는 동시 프로그래밍에서 공유 불가능한 자원의 동시 사용을 피하기 위해 사용되는알고리즘으로, 임계 구역(critical section)으로 불리는 코드 영역에 의해 구현된다.

53

Page 54: C++에서 Objective-C까지 번역자료

9 Objective-C 문자열9.1 Objective-C의 유일한 정적객체C 언어에서 문자열은 문자의 배열 또는 char* 포인터이다. 이러한 데이터를 다루는 것은 매우어렵고, 많은 버그를 발생시키는 원인이 된다. Objective-C의 string 클래스는 이 문제에 대한해결사이다. Objective-C에서는 객체가 자동적할당될 수 없고 런타임시에 할당되어야 한다고 29쪽 5절에서 이미 설명하었다. 이 동적할당 방식은 정적 문자열의 사용과 호환적이지 않다. 이때문에 정적 C 문자열이 NSString 객체의 생성 매개변수로 사용되는 상황을 만들기도 하는데이는 메모리의 낭비이며 다루기 불편하다.다행히도, 정적 Objective-C 문자열이 존재한다. 이 정적 문자열은 따옴표 사이에 있으며 추가

적으로 @접두사를 가지는 간단한 C 문자열이다.

NSString* notHandy = [[NSString alloc] initWithUTF8String:"helloWorld"];NSString* stillNotHandy = //initWithFormat은 sprintf()의 일종이다

[[NSString alloc] initWithFormat:@"%s", "helloWorld"];NSString* handy = @"hello world";

더욱이, 정적 문자열은 일반 객체처럼 메소드를 호출할 수도 있다.

int size = [@"hello" length];// uppercaseString은 소문자를 대문자로 변경하는 메소드임

NSString* uppercaseHello = [@"hello" uppercaseString];

9.2 NSString과 인코딩

NSString 객체는 다수의 편리한 메소드를 가지며 이 이외에도 또 다른 인코딩(ASCII, Unicode,ISO Latin 1. . . )을 지원하기 때문에 매우 유용하다. 이러한 문자열을 사용하는 것은 애플리케이션의 번역과 지역화(localization)에서 매우 유용하다.

9.3 객체의 설명, %@ 포맷 확장, NSString을 C 문자열로Java에서는 모든 객체가 Object로부터 상속받으며 toString 메소드의 도움을 받아 문자열로객체를 설명할 수 있는데 이것은 디버깅시에 아주 유용하다. Objective-C에서는 이 메소드를

description이라하며 NSString을 리턴한다.C 언어의 printf 함수는 NSString 객체를 지원하기 위해 확장시키지 않았다. printf와 같

이 포맷 문자열을 받아들이는 다른 함수로 Objective-C에서는 NSLog를 대신 사용할 수 있다.NSString 객체를 위해 사용하는 포맷은 “%s”가 아니라 “%@”이다. 그리고 더 일반적으로, 실제로 -(NSString*) description 호출의 리턴 값을 사용하고 있기 때문에 “%@”는 어떤 객체에든사용할 수 있다.더욱이, NSString은 UTF8String(이전의 cString) 메소드를 통해서 C-형식 문자열로 변환할

수 있다.

char* name = "Spot";NSString* action1 = @"running";printf("My name is %s, I like %s, and %s...\n",

name, [action1 UTF8String], [@"running again" UTF8String]);NSLog(@"My name is %s, I like %@ and %@\n",

name, action1, @"running again");

54

Page 55: C++에서 Objective-C까지 번역자료

10 C++ 고유의 특징

지금까지 여러분은 C++의 객체지향 개념이 Objective-C에 존재하는 것을 보았다. 그러나 일부기능은 Objective-C에 존재하지 않는다. 이 개념은 객체 지향 개념과는 상관이 없으나 일부 코딩기능을 살펴볼 필요는 있다.

10.1 참조

참조연산(&)은 Objective-C에 존재하지 않는다. 참조 카운팅과 autorelease를 사용하여 메모리를 관리하는 것은 메모리 관리면에서 다소 불편한 점도 있다. 그러나, Objective-C의 객체는 항상동적으로 생성되므로 포인터를 통해서만 참조된다.

10.2 인라인

Objective-C는인라인기능을구현하지않는다.이것은동적인 Objective-C의일부코드에서 “경직성(freeze)"을허용하지않기때문에메소드의입장에서는다소논리적이다.그러나,인라인함수는max(), min(). . .등과 같이 매우 간단하게 C로 작성된 유틸리티 함수를 사용할 경우는 유용할 것이다. 이것은 Objective-C++를 통해서 문제를 해결할 수 있다.그럼에도불구하고, GCC컴파일러가 C와 Objective-C를위해비표준키워드인 __inline또는

__inline__을인라이닝하도록제공한다는것에유의하기바란다.더욱이, GCC또한 C99코드를완벽하게 컴파일할 수 있는데, C99이 인라인 키워드(이 경우 표준 키워드로 채택됨)로 inline을지원하는 C의 수정판을 컴파일할 수 있다. 따라서, C99 코드에 기반을 둔 Objective-C는 인라이닝의 혜택을 받는다.그러나 성능향상을 위해서는, 인라인 기능이 아닌 IMP-caching 사용하는 것을 고려할 수 있다

(비고. 57쪽 11.3.2절).

10.3 템플릿

템플릿 기능은 효율성을 위해서 상속과 가상 메소드를 대체하는 기능이다. 그러나 이 기능은순수한 객체지향의 개념과는 분명히 거리가 멀다. (여러분은 템플릿의 미묘한 사용이 private 멤버에게 public 액세스 권한을 부여할 수 있다는 것을 아는가?) 메소드 이름 오버로딩과 선택자에대한 규칙이 매우 어렵기 때문에 Objective-C는 템플릿을 구현하지 않는다.

10.4 연산자 오버로딩

연산자오버로딩은 C++에서기존에있는연산자에새로운기능을추가하는것으로, Objective-C는연산자 오버로딩을 구현하지 않는다.

10.5 프렌드

C++의 프렌드 함수는 객체 내부의 private 멤베에 대한 무제한적인 접근을 허용하는 효율성

과 편리성이 있으나, 캡슐화라는 객체지향 개념을 무력화시키기 때문에 논란의 여지가 많다22.Objective-C에는 프렌드(friend)의 개념이 없다. Objective-C에는 존재하지 않는 프렌드 기능은연사자 오버로드의 효율성을 위해 C++에서는 유용하게 사용할 수 있는 기능이다. 그러나 객체의멤버에 대한 무제한적인 접근은 예상치 못한 문제를 만들 수 있다. Java에서는 패키지 기능이있는데 이 기능이 프렌드 기능과 약간 비슷하다. Objective-C에서는 클래스 카테고리라는 기능이자바의 패키지 기능과 비교될 수 있다.(비고. 27쪽 4.5절).

10.6 const 메소드

메소드는 Objective-C에서 const로 선언할 수 없다. 따라서, mutable 키워드도 존재하지 않는다.

10.7 생성자에서 초기화 리스트

생성자에서의 초기화 리스트는 Objective-C에서는 존재하지 않는다.22사실 객체지향의 관점에서는 friend 연산자를 살펴보면, 객체의 private 멤버에 대한 무제한적인 접근을 허용한다는점에서 매우 억지스러운 기능이라고 할 수 있다

55

Page 56: C++에서 Objective-C까지 번역자료

11 STL과 코코아C++ 언어에서 지원하는 STL은 표준 템플릿 라이브러리(Standard Template Library)의 약자로서많은 개발자들이 공통적으로 사용하는 자료구조와 알고리즘에 대한 클래스인데, 이 STL은 템플릿 기반의 일반화 프로그래밍(generic programming) 기법을 사용하여 작성되었다. 표준 C++ STL라이브러리는 나름대로의 장점이 있다. 비록 이것이 일정한 갭(함수를 예를 들면, 이 갭은 SGISTL [?]로 메워진다)을 가지고 있을지라도 아주 완벽하다. STL은 C++ 언어의 자체적인 기능은

아닌확장된기능인데,다른언어에서이와유사한기능을찾아보는것은일반적이다. Objective-C에서는 컨테이너, 반복자(iterator) 및 다른 사용가능한 알고리즘을 찾아서 코코아를 보아야 한다.

11.1 컨테이너

명백하게,코코아는객체지향방식으로만수행되기때문에컨테이너는객체만포함할수있으며,이러한 면에서 C++의 템플릿과는 다르다.프로그램 작성시 사용할 수 있는 컨테이너는 다음과 같다.

• 순서를 가진 컬렉션을 위한 NSArray 와 NSMutableArray

• 순서가 없는 컬렉션을 위한 NSSet 과 NSMutableSet

• 키/값과 결합된 컬렉션을 위한 NSDictionary 와 NSMutableDictionary

• 약한 참조를 사용하는 해시 테이블인 NSHashTable (Objective-C 2.0 전용객체, 아래참조).

여러분은 NSList또는 NSQueue가빠져있다는것을알수있다. Objective-C에서는이러한객체들대신순수 NSArray객체가이개념을구현하고있는것으로볼수있다. C++에있는 vector<T>와는 다르게, Objective-C의 NSArray는 그것의 내부 구현을 숨기고 접근자를 통해서만 컨텐츠에접근할 수 있다. 따라서, NSArray는 연속적으로 메모리 셀에 내용을 채워넣어야 할 의무가 없다.NSArray의 구현은 NSArray를 배열이나 리스트처럼 효과적으로 사용하도록 적절히 잘 절충하였다. Objective-C에서 컨테이너는 객체에 대한 포인터만을 저장할 수 있기 때문에, 셀의 조작은아주 효과적일 수 있다.

NSHashTable은 NSSet과 동일한것으로 볼 수 있다. 그러나 이것은 약한 참조(비고. 50쪽 6.6.2절)를 사용하기 때문에 가비지 수집 환경(49쪽 6.6절)에서 매우 편리하다.

11.2 반복자(Iterator)11.2.1 고전적인 열거

순수한 객체 지향방식에서는 보면 C++에 비해 Objective-C를 통한 반복자의 개념이 더욱 융통성있다. NSEnumerator는 다음과 같이 설계되었다.

NSArray* array = [NSArray arrayWithObjects:object1, object2, object3, nil];NSEnumerator* enumerator = [array objectEnumerator];NSString* aString = @"foo";id anObject = [enumerator nextObject];while (anObject != nil){[anObject doSomethingWithString:aString];anObject = [enumerator nextObject];

}

컨테이너(objectEnumerator)의 메소드는 반복자를 리턴하며, 스스로(nextObject) 이동할 수

있다. 이러한 기법은 C++ 보다 Java에 더 가깝다. 반복자가 컨테이너의 끝에 도달하게 되면

nextObject는 nil을 반환한다.함축하는 C의 기능에 근거를 두는 반복자를 사용하는 일반적인 문법이 있다.

56

Page 57: C++에서 Objective-C까지 번역자료

NSArray* array = [NSArray arrayWithObjects:object1, object2, object3, nil];NSEnumerator* enumerator = [array objectEnumerator];NSString* aString = @"foo";id anObject = nil;while ((anObject = [enumerator nextObject])) {[anObject doSomethingWithString:aString];

}// 이중 괄호를 사용하는 이유는 gcc 컴파일러의 경고가

// 나타나지 않도록 하기 위해서이다.

11.2.2 빠른 나열형

Objective-C 2.0(비고. 6쪽 1.2절)은 컨테이너를 열거하기 위해 NSEnumerator를 내포한(게다가,더 이상 NSEnumerator가 아닙니다) 새로운 문법을 소개했다. 다음은 새로운 문법 형식이다.

NSArray* someContainer = ...;for(id object in someContainer) { //here, each object is typed "id"...

}for(NSString* object in someContainer) { //here, each object is typed "NSString*"...//if an object is not an NSString*, it’s up to the developer to handle that

}

11.3 함수자 (함수 객체)11.3.1 셀렉터 사용하기

Objective-C의 셀렉터의 힘은 함수자를 덜 유용하게 만듭니다. 실제로, weak typing은 진짜 처리하는 수신기의 능력에 주의하지 않고 사용자에게 메시지를 보내는 것을 허용한다. 예를 들면,반복자를 사용하여 이전 것과 동일한 코드가 여기 있다.

NSArray* array = [NSArray arrayWithObjects:object1, object2, object3, nil];NSString* aString = @"foo";[array makeObjectsPerformSelector:@selector(doSomethingWithString:)

withObject:aString];

이 경우에는, 객제는 클래스와 동일한 종류가 아니어도 되고, doSomethingWithString: 메소드(단지 “셀렉터를 인식하지 못하는” 예외가 발생할 것이다)를 구현하지 않아도 된다!

11.3.2 IMP 캐싱

여기서 자세히 나오지는 않지만, 메모리로 메소드를 나타내는 C 함수의 주소를 얻는 것이 가능한다. 메소드를 한번만 찾아서, 객체의 무리에 동일한 selector의 다중 호출을 최적화하기 위해서사용될 수 있다. 이것은 IMP가 Objective-C에서 메소드 구현의 데이터 타입이기 때문에 “IMP캐싱”이라고 한다.

class_getMethodImplementation() (비고. 66쪽 13.2절)에 대한 호출은 이런 포인터를 얻으려면 인스턴스에 사용할 수 있다. 구현 메소드에 대한 실제 포인터이다. 주의 사항 : 그것은 가상호출을 해결하지 않는다. 그것의 사용은 대부분의 최적화 시간에 관련이 있고 신중하게 이루어져야 한다.

11.4 알고리즘

STL의일반적인알고리즘세트는실제로코코아에서대응할만한것이없다.대신각컨테이너에의해 제공되는 메소드를 보아야 할 것이다.

57

Page 58: C++에서 Objective-C까지 번역자료

12 Implicit code이절에서는코드를좀더간단하게만들기위한 Objective-C의두가지기능을설명하려고한다.이두방법의최종목표는다르다:키-값코딩은(비고. 58쪽 12.1절)처음으로일치하는사용가능한구현부를 선택하는 방식으로 간접 메소드 호출 문제을 구현하는데 사용되며, 프로퍼티(properties)는 (비고. 60쪽 12.2절) 설정자(setter)와 접근자(getter)를 반복적으로 만들때 사용되는 지루한코딩 작업 일부를 자동으로 대신해준다.

Key-value코딩은사실코코아프레임워크에서제공되는기능이며,프로퍼티표현은Objective-C 2.0에 추가된 기능으로 프로그래밍 언어 자체에서 지원하는 기능이다.

12.1 Key-value 코딩12.1.1 원리

Objective-C에는데이터멤버를그이름을통해접근할수있도록하는방법이제공되는데이방식의이름이바로Key-value코딩이다.이것은데이터멤버의이름이키(key)라는점에서 NSDictionary와같은연관배열(비고. 56쪽 11.1절)과약간유사하다. Objective-C의모든클래스의부모클래스인 NSObject는 valueForKey:와 setValue:forKey:: 라는 이름의 메소드를 제공한다. 이 메소드를통해 우리는 데이터 멤버의 이름을 키로 하여 멤버를 읽어올 수 있는데, 이때 데이터 멤버가 객체인 경우에는 그 내부 값으로의 탐색도 이루어질 수 있다. 이와 같이 데이터 멤버의 내부 값에접근하는 경우에는 키 대신 “key path”를 키로 사용하며, 구성요소들은 도트로 구분해야 한다.메소드는 valueForKeyPath:와 setValue:forKeyPath:를 사용할 수 있다.

@interface A {NSString* foo;

}

... //완성된 코드를 위해 메소드가 반드시 필요함

@end

@interface B {NSString* bar;A* myA;

}

... //완성된 코드를 위해 메소드가 반드시 필요함

@end

@implementation B...//A 타입의 객체 a와 B 타입의 객체 b를 가정하자

A* a = ...;B* b = ...;NSString* s1 = [a valueForKey:@"foo"]; //성공NSString* s2 = [b valueForKey:@"bar"]; //성공NSString* s3 = [b valueForKey:@"myA"]; //성공NSString* s4 = [b valueForKeyPath:@"myA.foo"]; //성공NSString* s5 = [b valueForKey:@"myA.foo"]; //에러!NSString* s6 = [b valueForKeyPath:@"bar"]; //성공, 안될 이유가 없다

...

@end

58

Page 59: C++에서 Objective-C까지 번역자료

이러한 구문이 지원되기 때문에, 한 클래스의 인스턴스 데이터를 위해 같은 이름을 사용하는다른클래스의일부객체를처리하기위해동일한코드를사용하는것이가능해졌다.위의예에서s5의 경우는 에러가 나는데 myA.foo와 같이 b 객체의 멤버인 myA 내부에 있는 속성값을 얻기위해서는 valueForKeyPath:를 사용하여야 한다. 이와 같은 방법으로 내부의 속성값에 손쉽게접근하는 기법은 Objective-C가 이에 관한 런타임 엔진을 가진 매우 동적인 언어이기 때문이다.데이터와 일부 함수를(특히 메소드 호출) 바인딩시키는 최상의 사용 예는 여기 자세히 다루지

는 않았지만 Key-Value Observing(KVO)과 같은 몇 가지 방식이 있다.23

12.1.2 차단

valueForKey: 또는 setValue:forKey:를 호출해 데이터에 접근하는 것은 더이상 분해가 불가능한 아토믹 조작(atomic operation)이 아니다. 이 액세스는 호출 규약 절차에 따른다. 실제로, 이액세스는 몇몇 메소드가 실행되거나(메소드는 properties를 사용할 때 자동으로 생성될 수 있다.비고. 60쪽 12.2절), 인스턴스 데이터에 대한 직접 접근이 명백하게 허용되는 경우에만 가능하다.

Apple 문서는 valueForKey:와 setValue:forKey: [3]가 하는 동작을 정확하게 잘 설명하고 있다. 실제로 valueForKey:@"foo"를 메소드가 호출되면 다음과 같은 일을 수행한다.

• 가장 먼저 getFoo 메소드가 존재하는지 살펴보고 이 메소드가 존재하는 경우 이것을 호출한다.

• 그렇지 않으면, foo(가장 일반적인 경우) 메소드를 호출(메소드가 존재하는 경우)한다.

• 다음으로, isFoo(일반적으로 부울값) 메소드를 호출(메소드가 존재하는 경우)한다.

• 다음으로, accessInstanceVariablesDirectly 메소드를 사용해 보는데, 이때 이 메소드가YES를 반환하는 경우에 데이터 멤버(존재하는 경우)인 _foo, 아니면 _isFoo, 아니면 foo,아니면 isFoo를 읽는 작업을 차례로 시도해본다.

• 데이터 멤버에 차례로 접근을 시도하는데, 이때 이전 단계에서 성공한 경우에는 일치하는값을 반환한다.

• 마지막으로 실패한 경우에는 valueForUndefinedKey: 메소드가 호출된다. 디폴트 실행은NSObject의 예외를 발생시킨다.

다음으로 setValue:..forKey:@"foo"에 대한 호출은 다음과 같다.

• setFoo: 메소드가 존재하면 이 메소드를 호출한다.

• 그렇지않으면, accessInstanceVariablesDirectly메소드를사용해보는데,이때이메소드가 YES를 반환하는 경우, 데이터 멤버(존재하는 경우) _foo, 아니면 _isFoo, 아니면 foo,아니면 isFoo를 설정하는 작업을 차례로 시도한다.

• 위의 과정을 통한 접근이 모두 실패한 경우에는 setValue:forUndefinedKey: 메소드가 호출된다. 이 예외를 발생시키는 NSObject의 디폴트 구현부가 존재한다. 예외 대신 별도의작업을 진행하려면 setValue:forUndefinedKey: 메소드를 재정의 하자.

valueForKey: 또는 setValue:forKey:를 호출하면 어떤한 호환 메소드이든지 실행(트리거)시킬 수 있다. 만일 어떤 객체에 해당하는 데이터 멤버가 없다면, 그것은 “더미(dummy)” 메소드가 될 것이다.예를들어,문자열객체에대하여 valueForKey:@"length"메소드를적용시키면,length라는 키가 KVC를 리졸빙할 때 발견되는 첫 번째이기 때문에 직접적으로 length라는 메소드를 호출하는 것과 같다고 할 수 있다.그러나, KVC의 성능은 딕셔너리에 키(key)로 접근한 후 검색 결과가 YES일 경우에만 비로소

밸류(value)에 해당하는 메소드를 호출하므로, 이 메소드를 직접 호출하는 것 만큼은 성능이 좋지않으므로 사용할 적에 신중하게 사용해야 한다.

23만약, 임의의 객체의 프로퍼티 값이 변화가 있는지 수시로 알아야 할 필요가 있다고 가정해 보자(이를 위해서는 수시로값의변화를확인하는풀링(pooling)이라는방식을사용하여야하는데,이방식은매우비효율적이다).여기서풀링이아니라,이벤트드리븐방식을사용하면어떨까?이를위해서는프로퍼티값이변경됐을때해당객체가책임지고,관계있는 다른 객체들에게 값의 변화를 통보해 주어야 한다. 여기서 NSNotification도 비슷한 역할을 하지만 NSNotification은 중앙 정부에 해당하는 NSNotification 센터가 있어서 센터가 통지를 처리하는 방식이고, Key-Value Observing은해당 객체가 직접 신호를 보낸다는 점에서 차이가 있다. 자바 프로그래밍 언어에서 사용하는 XXXActionListener와 같은것들이 이와 유사한 기능에 해당한다. [譯註]

59

Page 60: C++에서 Objective-C까지 번역자료

12.1.3 프로토타입

KVC 방식으로 호출하는 메소드는 미리 예상할 수 있는 프로토타입에 따라야만 한다. 예를 들어접근자(getters)는 아무 매개변수도 없이 객체를 리턴만하며, 설정자(setter)는 파라미터를 하나가지는데 리턴형은 void형인 메소드로 내부 객체에 파라미터를 설정하는 역할을 한다. 매개 변수의 정확한 타입이 무엇이든 그것인 id형(NSObject*형으로 모든 객체에 대한 포인터 변수임)이기때문에 프로토타입의 타입은 그다지 중요하지 않다.그러나객체의내부속성으로사용되는구조체(struct)와네이티브타입(int, float. . .등이네

이티브 타입이다)까지 지원된다는 점에는 주목하여야 할 것이다. Objective-C 런타임은 네이티브타입을 NSNumber 또는 NSValue 객체로 자동 객체화(boxing)을 실행할 수 있다. 이러한 점 역시Objective-C의 뛰어난 특성이다. 따라서, valueForKey:가 반환하는 값은 항상 객체이다.

setValue:forKey:메소드를통해서특별히 nil값을주려고하는경우에는 setNilValueForKey:메소드를 통해서 처리하면 된다.

12.1.4 고급 기능

이밖에도 쓸만한 고급 기능에 대해서 여기서는 자세히 다루지 않겠지만 고려해볼만한 몇 가지

세부내용은 다음과 같다.

• 우선 덧셈 연산이나, 평균값, 최대나 최저. . .와 같은 특별한 처리를 포함할 수 있는 keypaths에 관한 것이다. 여기서 사용되는 @문자는 구별 표식이다.

• 다음으로 valueForKey:또는 setValue:forKey:메소드호출은연관배열의일종인 NSDictionary와 같은 컬렉션에서 사용되는 objectForKey:와 setObject:forKey: 메소드와 연관성이 높으며 사용법도 비슷하다(비고. 56쪽 11.1절). 여기에서도, @는 애매함을 해결하기 위하여사용된다.

12.2 프로퍼티

12.2.1 프로퍼티의 활용

property의 개념은 클래스 정의를 할때 만나게 된다. @property(와 속성에 관한 내용은 12.2.3참조)라는 키워드는 컴파일러가 데이터 멤버에 접근할 때 자동적으로 어떤 형태의 접근자를 제공할지를 결정하게 된다. 이 기능은 프로그램 개발시 코드의 양을 줄일 수 있고 시간도 절약할 수있다.

C#에서도 setter, getter를 편리하게 생성하는 기능이 제공되고 있는데 Objective-C는 이 기능이좀더강력하다고할수있다.그러나 C++에는이기능이없으므로 C++개발자입장에서는다소

생소한 문법으로 생각될 수 있다. 프로퍼티의 구문은 함수 호출과 비교해 볼때 좀 더 간단하다.따라서 우리는 궁극적으로 우리가 숨기기 원하는 코드를 작성해야만 하는 경우라 하더라도 프로

퍼티를사용하는것이더편리할수있다.프로퍼티의성능역시컴파일시에이메소드가자동으로생성되기 때문에 함수 호출과 동일한 성능을 보인다.대부분의 경우 프로퍼티는 데이터 멤버로 한정되지만, 만일 접근자를 재정의하는 경우에는

속성을 “더미(dummy)”로 만들어도 아무 상관없다. 다른 말로 하자면, 프로퍼티는 객체의 외부로부터의 속성처럼 보인다. 그리고 내부로부터의 단순한 값 관리보다 훨씬 더 복잡한 행동까지포함할 수 있는 강력한 기능까지 가질 수 있다.

12.2.2 프로퍼티의 서술

프로퍼티를 서술한다는 의미는 컴파일러에게 접근자를 어떻게 만들어야하는지 알려주는 것이다.

• 이 속성에 대해 외부에서 “읽기 전용”으로 접근하도록 할 것인가?

• 만일 데이터 멤버가 정수형이나 실수형, 구조체와 같은 네이티브 형이면 접근자와 설정자는 거의 변화가 없다. 그러나 멤버가 객체일 경우 복사나 강한 참조, 약한 참조 속성등을이용하여 캡슐화할 수 있다(이는 메모리 관리와 관련이 있다. 비고 45쪽 6.4.7절).

• 멤버 변수가 thread-safe24 (비고. 53쪽 8.1절) 해야 하는가?24 스레드안전(thread-safe)은멀티스레드프로그래밍에서일반적으로어떤함수나변수,혹은객체가여러스레드로부터동시에접근이이루어져도프로그램의실행에문제가없음을뜻한다.보다엄밀하게는하나의함수가한스레드로부터

60

Page 61: C++에서 Objective-C까지 번역자료

• 접근자(accessor)의 이름은 무엇인가?

• 어떤 데이터 멤버가 접근자와 결합되어야 하는가?

• 어떤 접근자가 자동적으로 생성되어야 하는가, 그리고 어떤 접근자를 개발자가 직접 만들어야 하는가?

위의 여러 질문들을 해결하는 절차는 두 단계로 진행된다.

• 클래스의선언부인 @interface블럭에서,프로퍼티는적절한속성으로선언된다(뒤에옴. 62쪽 12.2.3절).

• 그클래스의구현부인 @implementation블럭에서접근자는내부에서자동적으로만들어지는데, 경우에 따라서 이것을 직접 구현할 수도 있다(비고. 63쪽 12.2.4절).

접근자의 프로토타입은 엄격히 정의되어 있다. 다시 말하면 접근자(getter)는 예상되는 형(또는호환가능한형)을정확하게리턴해야만하고,설정자(setter)의경우 void를리턴하고예상되는형(또는 호환되는 형)을 하나의 매개변수로 가지도록 제한적으로 정의하여 설정해야만 한다.이때 접근자의 이름도 명문화된다. foo라는 이름의 데이터를 예를 들면, 이 데이터의 접근자

메소드 이름은 foo이고 설정자를 위한 메소드는 setFoo:이다. 이와 같은 엄격한 접근자, 설정자이름 이외에 다른 이름을 접근자, 설정자로 사용하기 원하는 경우 다른 이름을 사용할 수도 있다.그러나 properties는 메소드를 빨리 생성하는 편의성을 목적으로 설계되었기 때문에, 컴파일시에이름이알려져있어야하며,이이름을이용하여메소드호출을직접하는구조로되어있다.이와는달리 동적인 기법을 사용하는 Key-Value Coding (59쪽 12.1.2절)은 실행시에 키에 대한 밸류가알려져야만 한다는 것을 명심하여야 한다. 이 때문에, 호환되지 않는 형을 매개변수로 적용하여형을 변환시키는 박스화(boxing)는 프로퍼티에 적용되지 않는다.다음에 몇 가지 설명과 예제가 나타나지만 이 예제들은 전체에 걸친 작동방식에 대한 간략한

미리보기와 같은 역할을 한다. 다음 절에서는 완전한 이해를 위해 필요한 세부 사항을 자세히설명하겠다.호출되어 실행 중일 때, 다른 스레드가 그 함수를 호출하여 동시에 함께 실행되더라도 각 스레드에서의 함수의 수행 결과가 올바로 나오는 것으로 정의한다. 스레드 안전을 유지하기 위해서는 객체나 변수에 대한 동시 접근을 제어하기 위한감시기능이추가되어야하는데이기능은시스템의기능을저하시키게된다.따라서,시스템의성능향상을위하여스레드안전을 제거하는 기능이 nonatomic이다. [譯註]

61

Page 62: C++에서 Objective-C까지 번역자료

@interface class Car : NSObject{NSString* registration;Person* driver;

}

// registration은 읽기전용 필드이며, 복사속성으로 설정된다

@property (readonly, copy) NSString* registration;

// driver는 (retain 없는)약한 참조이며 수정될 수 있다

@property (assign) Person* driver;

@end

...

@implementation

// 컴파일러는 개발자가 이 코드를 직접 만들지 않은 경우

// "registration"을 위한 getter/setter 코드를 자동으로 생성한다

@synthesize registration;

// 개발자는 "driver"를 위한 getter/setter를 직접 구현해야 한다

@dynamic driver;

// 이 메소드가 @dynamic driver의 getter부에 해당한다

-(Person*) driver {...

}

//이 메소드가 @dynamic driver의 setter부에 해당한다

-(void) setDriver:(Person*)value {...

}

@end

12.2.3 프로퍼티 속성

프로퍼티는 다음의 템플릿 형태로 선언한다.@property 형 이름;또는

@property (속성) 형 이름;

이 템플릿이 주어지지 않은 경우 속성은 디폴트 값을 가지며, 템플릿이 주어지면 속성은 이전절에서 언급한 질문에 대한 답변을 재정의 할 수 있다. 이 형태는 다음과 같다 :

• readwrite (디폴트 속성) 또는 readonly는 프로퍼티가 getter/setter 둘 다 또는 getter만을가져야 한다는 것을 말한다.

• assign(디폴트), retain, copy는 값이 내부에 어떻게 저장되는지 알려준다.

• thread-safety가 발생하는 것을 방지하기 위해 nonatomic이 생성된다. nonatomic은 디폴트로 생성된다.(atomic 키워드는 없다);

• getter=..., setter=... 는 접근자의 디폴트 이름을 변경한다.

62

Page 63: C++에서 Objective-C까지 번역자료

setter 내부의 assign, retain 또는 copy는 데이터 멤버를 수정하는 방법에 영향을 미친다.-(void) setFoo:(Foo*)value 메소드에는 세 가지 방식이 있다.

self->foo = value ; //간단한 할당

self->foo = [value retain]; //레퍼런스 카운터를 증가시킨 할당

self->foo = [value copy]; //객체의 복사는 NSCopying 프로토콜을// 따라야 한다 (비고, 36쪽 5.3.1절)

가비지-수집기 환경(비고, 49쪽 6.6절)에서는 retain와 assign이 다르지 않다. 그러나 그경우, 속성값으로 __weak와 __strong이 추가될 수 있다.

@property(copy,getter=getF,setter=setF:) __weak NSString* f; //복잡한 선언

(“setF:” 문법에서 매개변수가 있으므로 콜론이 사용되는 점에 유의하여야 한다)

12.2.4 프로퍼티의 사용자 지정 구현

60쪽 12.2.2절의 조각 코드의 구현은 두 가지 키워드인 @synthesize와 @dynamic에만 의존한다.@dynamic은 구현부를 개발자가 런타임 시에 제공한다는 것을 의미하는데 애플리케이션의 실

행환경에 따라 동적이어야하는 경우 사용하는 지시어이다(프로퍼티 선언시 read-only(읽기 전용)로 선언하며, 단순한 setter만 사용자가 만들면 되지만 그렇지 않을 경우 getter와 setter를 별도로만들어야 한다).

@synthesize는 개발자가 이미 만들어두지 않은 경우 컴파일러가 프로퍼티 선언시에 사용된제약조건에따라접근자를생성해야한다는것을의미한다.따라서,앞절에서살펴본예제와같이개발자가 -(NSString*)registration 메소드를 구현한 경우 컴파일러는 새로운 지정자(setter)메소드를 생성하는 대신 이미 만들어진 것을 선택한다. 그러므로 우리는 필요로하는 두 가지 메소드 중에서 개발자에 의해 하나의 접근자가 이미 생성된다는 것을 알 수 있다.접근자가 컴파일시에 만들어지지도 않고 @synthesize를 이용하여 컴파일러에 의해 자동생성

도 되지않은 경우, 마지막 방법으로 런타임시에도 접근자를 추가시킬 수 있다(비고. 66쪽 13.2절). 이것은 Objective-C의 매우 동적인 특성 때문이다. 이러한 방법을 통해서 프로퍼티에 접근하는 것은 충분히 가능한데, 이 경우에도 예상되는 접근자의 이름 만큼은 컴파일시에 결정된다.런타임에접근자가없는경우,예외가발생하지만프로그램은멈추지않는다.이경우존재하지

않는 메소드를 호출한 것과 동일한 동작을 한다.@synthesize를 사용할 때, 컴파일러는 같은 이름이 없는 특정 데이터 멤버에게 프로퍼티를

바인딩하도록 요청하며 이를 이용하여 접근자를 생성한다.

@interface A : NSObject {int _foo;

}@property int foo;@end

@implementation A@synthesize foo=_foo; //"foo"가 아닌 "_foo"에 바인드 된다

//(여기서는 존재하지 않은 속성임)@end

12.2.5 프로퍼티에 접근하는 문법

프로퍼티의값을얻거나(get),지정(set)하려면 dot문법을사용한다.이는간단한 C struct와같은문법이고, keypath 원칙과도일관성이있다(비고. 58쪽 12.1.1절).이경우의성능은기본메소드에대한 직접 호출과 동일한다.

63

Page 64: C++에서 Objective-C까지 번역자료

@interface A : NSObject {int i;

}@property int i;@end

@interface B : NSObject {A* myA;

}@property(retain) A* a;@end

...A* a = ...B* b = ...;a.i = 1; // [a setI:1]과 동등한 문법

b.myA.i = 1;// [[b myA] setI:1];과 동등한 문법

위 예제를 바탕으로 클래스 A의 내부를 살펴보자. 이 경우 self->i와 self.i 사이에는 큰차이가 있다. 실제로 self->i는 데이터 멤버에 대한 직접 접근과 같으며, self.i는 프로퍼티메커니즘에 의해 구동되는 메소드 호출과 같다. 사실 C++에서 self->i는 포인터 변수의 멤버값을 의미하는 (*self).i의 다른 표현이다. 반대로 Objective-C에서 사용되는 self.i는 메소드호출문과 동일하다. 즉 메소드가 설정자(setter)로 사용되는 경우에는 [self setI:]와 동일하며,접근자(getter)로 사용되는 경우에는 접근자 메소드 [self i]를 실행시키는 것과 동일하다. 만일@property 선언과 @synthesize 구문을 통하여 객체내부의 멤버에 접근하는 통로를 만들지 않고self.i와 같이 접근하게 되면 접근자 또는 설정자가 없다는 컴파일 에러가 나게된다.

@interface A : NSObject {@publicint i;

}

@end

@implementation A...@end

A* a = [[A alloc] init]; // 객체생성a.i = 1; // 객체의 설정자 [a setI:]가 없음:오류a->i = 1; // 객체의 멤버변수에 직접 접근하여 값을 설정함: 실행됨

// 이 방식은 캡슐화를 위반하는 것으로 객체지향적인 방식이 아님

(*a).i = 1; // 위의 문법 a->i과 동일함

12.2.6 고급 내용

프로퍼티에관한 [4]문서는 64비트컴파일을위한것으로, Objective-C 런타임에는 32비트모드와조금 밖에 차이가 없다. @property 선언과 관련있는 인스턴스 데이터는 이 데이터들이 암시적(implicit)일수있다는점때문에생략될수있다.애플사문서는모든정보를얻기위해필수적으로읽어야 하는 참고문헌으로 남겨둔다.

64

Page 65: C++에서 Objective-C까지 번역자료

13 동적성질

13.1 RTTI (런타임 형 정보)Objective-C에 비교하면, C++은 매우 정적인데 이런 면에서 C++ 언어는 “가짜” 객체지향 언어로볼 수 있다. Objective-C의 동적인 성질은 런타임시에 최상의 성능을 얻기위하여 고려한 최선의선택이다. C++가 제공할 수 있는 런타임 정보는 typeinfo 라이브러리를 통하여 할 수 있다. 그러나 이러한 런타임 정보는 컴파일러에 의존적이기 때문에 보편적으로 사용할 수 없다. 객체의클래스에 대한 정보요청은 스트롱 타이핑(strong typing)으로 인하여 매우 제한적으로 할 수 밖에없다. 이러한 요청은 드문 요청이지만 컨테이너를 탐색(브라우징)할 때 발생할 수 있다. C++에서

도 dynamic_cast 연산자와 함께 가끔씩 typeid를 사용할 수 있다. 그러나 프로그램 사용자와의상호 작용은 제한적이다. 객체가 클래스의 인스턴스인지 아닌지를 그 이름만으로 어떻게 알 수있을까?

Objective-C는 애초부터 이러한 질문을 고려하여 설계되었다. Objective-C에서는 클래스 역시객체이며 그것들은 자신의 행동을 상속한다.하향캐스팅은 dynamic_cast의 특별한 활용사례이므로 21쪽 3.4.4절에 설명되어 있다.

13.1.1 class, superclass, isMemberOfClass, isKindOfClass

런타임시에자신의타입에대하여알고자하는객체가가진기능은 introspection이라고하는몇몇메소드로 처리할 수 있다.

isMemberOfClass:는 “나는 주어진 클래스(상속은 제쳐두고)의 인스턴스인가?”라는 질문에응답하는 메소드이다. , isKindOfClass:는 “나는 주어진 클래스의인스턴스 또는 그 서브 클래스중 하나인가?”에 대한 대답이다.이 메소드를 사용하기 위해서는 거짓 키워드인 class(전치 선언에 사용하는 @class가 아니

다)를 필요로 한다. 실제로, class는 NSObject의 메소드이고, 메타 클래스의 인스턴스인 Class객체를 리턴한다. 클래스 포인터를 위한 nil 값은 nil 이 아니라 Nil.임에 유의하자.

BOOL test = [self isKindOfClass:[Foo class]];if (test)printf("나는 Foo 클래스의 인스턴스이다\n");

또한 어머니 클래스의 클래스 포인터를 얻을 수 있는 방법이 있다는 것을 유의하자. 여러분은superclass 메소드를 사용할 수 있다.

13.1.2 conformsToProtocol

이 메소드는 세부 프로토콜 섹션에서 설명한다(23쪽 4.4절). 객체가 프로토콜에 따르는지 아닌지 아는것은 매우 유용하다. 명시적 적용만 검사하지 않고, 컴파일러가 각 메소드의 존재여부를모두 검사하는 방법은 사실상 동적이지 않는다. 객체가 주어진 프로토콜의 모든 메소드를 구현해 놓고도 그 프로토콜을 따른다는 것을 명시적으로 밝히지 않을 경우, 프로그램은 정확하지만conformsToProtocol:은 NO를 리턴한다.

13.1.3 respondsToSelector, instancesRespondToSelector

respondsToSelector:는 NSObject로부터상속된인스턴스메소드이다. respondsToSelector:를통해 객체는 주어진 메소드가 구현되었는가 여부를 확인할 수 있다. 이를 위하여 셀렉터 개념을사용한다(비고. 15쪽 3.3.4절). 예를 들어 다음과 같은 문장을 살펴보자.

if ( [self respondsToSelector:@selector(work)] ){printf("나는 부지런해.\n");[self work];

}

65

Page 66: C++에서 Objective-C까지 번역자료

상속 메소드의 검사없이 클래스가 주어진 메소드를 구현하였는지 알기위해서는, 클래스 메소드인 instancesRespondToSelector:를 사용할 수 있다.

if ([[self class] instancesRespondToSelector:@selector(findWork)]){

printf("I can find a job without the help of my mother\n");}

respondsToSelector:에 대한 호출은 클래스가 forwarding을 통해 메시지를 처리할 수 있는지여부를 결정할 수 없음에 유의하자(비고. 20쪽 3.4.3절).

13.1.4 id형을 가진 강한 타이핑과 약한 타이핑

C++은 강한 타이핑(strong typing)을 사용는데, 이 방식은 명백한 타입에 따라 객체를 사용할 수있고, 그렇지 않으면 프로그램이 컴파일되지 않는다. Objective-C에서, 타입이 명시적으로 알려진 객체는 한눈에 그것이 처리할 수 없는 메시지의 타겟인 경우 컴파일러는 경고를 출력하지만

프로그램은 실행될 것이다. 포워딩이 발생하는 경우를 제외하고 메시지는 손실될 것이다(예외를발생시킨다)(비고. 20쪽 3.4.3절). 개발자가 그것을 생각을 가지고 있었다면, 경고가 과할 것이다;이 경우에는, 실제 타입대신 id에 타겟을 입력하는 것은 경고를 제거한다. 사실, 어떤 객체든지id형이고, 어떤 메시지의 타겟일 수 있다.약한 타이핑 프로퍼티(weak-typing property)는 델리게이션(delegation)을 사용할 때 필수적인

데, delegate 객체는 사용되기위해 알려져 있을 필요가 없다. 여기에 예제이다 :

-(void) setAssistant:(id)anObject{[assistant autorelease];assistant = [anObject retain];

}

-(void) manageDocument:(Document*)document{if ([assistant respondToSelector:@(manageDocument:)])[assistant manageDocument:document];

elseprintf("Did you fill the blue form ?\n");

}

델리게이션은 그래픽 인터페이스가 사용되는 코코아에서 아주 일반적이며, 컨트롤이 작업자 객체에 사용자의 입력을 전송할 수 있다.

13.2 런타임시 Objective-C 클래스의 조작우리는 헤더 <objc/objc-runtime.h>를 포함하는 방법을 통해 클래스 정보와 관련된 몇 가지유틸리티 함수를 호출할 수 있고, 런타임에 메소드 또는 인스턴스 변수를 추가할 수 있다.

Objective-C 2.0은 Objective-C 1.0 기능의 대부분을 덮어쓰는 방식으로 Objective-C 1.0(예를들어 class_addMethods(...) 대신 class_addMethod(...)처럼 다루기 쉬운 새로운 기능을 도입)보다 쉬운 새로운 함수를 도입하였다.

Objective-C 2.0은 런타임에 클래스를 변경하는 것이 월등하게 쉽다. 비록 일반적이지는 않을지라도 이 동적 기능은 진짜 유용하게 쓰이는 경우가 많이 있다.

66

Page 67: C++에서 Objective-C까지 번역자료

14 Objective-C++

Objective-C++는 아직도 개발중인 언어이다. Objective-C++는 사용할 수는 있으나 아직 몇 가지

부족한 점이 있는것도 사실이다. Objective-C++는 Objective-C와 C++ 두 가지 언어를 병행하여

사용하기 위하여 두 언어중에서 더 나은 것을 취한다.C++와 Objective-C 예외를 혼합할 때, 또는 Objective-C 객체의 인스턴스 데이터로 C++ 객

체를 사용할 때 예상되는 결과를 이해하기 위해서는 보다 더 자세한 기술 문서를 참조하여야

한다. 그러나, Objective-C++로 프로그램을 작성하는 것은 분명히 가능한다. 구현된 파일은 확장자로 .mm을 가진다. MacOS X 10.4부터, C++ 객체는 Objective-C 클래스의 인스턴스 데이터로사용되어, 자동 생성/소멸25의 혜택을 받을 수 있다.

15 Objective-C의 미래본 저자는 버전 3.0 또는 다른 버전의 Objective-C의 미래와 발전방향에 대한 아무런 정보도 가지고 있지 않다. 그러나, Objective-C는 자동적으로 C 언어의 개선과 발전으로부터 기능 개선의혜택을 받게된다. GCC426과 LLVM527프로젝트는 이런 점에서 매우 흥미롭다.

GCC는 표준을 따르기 위해, 지속적으로 C++0x의 지원을 개선하고 있다. Objective-C++는 그

혜택을확실히받게될것이다.또한, LLVM은최근블록(block)의개념을통해클로져를통하여혜택을 받고있다(비고. 67쪽 15.1절). 이것은 C언어의 중요한 개선사항이고, Objective-C는 그것을사용할 수 있다.따라서, Objective-C 언어는 직접 또는 간접적으로 아직 개발 중이라고 할 수 있으며, 앞으로

그 기능이 더욱 풍부해 질 것이라고 예상할 수 있다.

15.1 블록(block)15.1.1 지원과 사용사례

MacOS X 10.6이후부터,그랜드센트럴디스패치(Grand Central Dispatch)와같은네이티브 API는 스타(*)대신 꺽쇠기호(ˆ)로 대체하여 함수에 대한 포인터처럼 보이는 새로운 문법인 블록을사용할 수 있다.다음은 그 예이다.

void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));

여기서 마지막 매개변수는 함수를 가리키는 포인터가 아니며, 블록이다.

블록은 코코아나 Objective-C 어느 쪽과도 상관없는 개념이다. 즉 모든 컴파일러에서 다 지원되지는 않는, 비표준 C 언어의 특성이다. 하지만 현재 MacOS X 10.6과 함께 배포된 LLVM컴파일러는 이 구문을 지원한다.아는사람은알겠지만,이문법은널리알려진 Javascript에있는클로져(closure)를 C에서구현

한 것이다. 쉽게 말하면, 클로저는 실행시에 곧바로 환경(변수 값)을 캡쳐하는 익명의 함수로 볼수 있다.블록은 콜백함수를 대치하는 매우 유용한 개념이며, 좀 더 일반적으로 말하자면 지연되거나

또는 비동기적 함수 호출을 위해 반드시 작성되어야 하는 코드를 줄이는데도 유용하다.

25http://developer.apple.com/releasenotes/Cocoa/RN-ObjectiveC/index.html26GCC 컴파일러는 2010년 12월 현재 4.5.2버전까지 개발되었으며, C, C++, Objective-C, Fortran, Java, Ada, Go언어를 지원한다. http://gcc.gnu.org/

27LLVM은 Low Level Virtual Machine(저수준 가상 기계)의 약자로 컴파일, 링크, 런타임 등 각 시점에서 프로그램을최적화하도록 설계된 모든 프로그래밍 언어에 대응 가능한 컴파일러 하부구조(infrastructore)이다. http://llvm.org/

67

Page 68: C++에서 Objective-C까지 번역자료

15.1.2 구문

블록은 다음과 같은 구문에 의해 생성된다.

^{...some code...}

또는

^(parameter1,[other parameters...]){...some code...}

블록은꺽쇠(ˆ)를별표(*)로대체하는상황에서함수포인트와같은변수내에저장될수있다.

typedef void (^b1_t)(void);b1_t block1 = ^{printf("hello\n");};block1(); // hello를 출력함

typedef void (^b2_t)(int i);b2_t block2 = ^(int i){printf("%d\n", i);};block2(3);// 파라미터인 3을 출력

15.1.3 환경의 캡쳐링

블록은생성될때존재하는변수들을참조할수있다.블록은변수의상태를 “캡쳐(captures)"하여그것들을 읽기 전용(read-only)으로 사용할 수 있다. 블록의 일부가 아닌 변수를 수정하는 것은특별한 명령을 필요로한다(비고. 68쪽 15.1.4절).

//예제 1int i = 1;b1_t block1 = ^{printf("%d", i);};block1();//display 1i = 2;block1();//여전히 1을 출력! 변수는 캡쳐된것이기 때문

//예제 2int i = 1;b1_t block1 = ^{i=2;}//불가능 : 컴파일이 안된다

//변수는 이렇게 수정할 수 없다.

//예제 3typedef void (^b1_t)(void);b1_t getBlock() // 이 함수는 한 블록을 반환한다

{int i = 1; //이 변수는 함수의 지역 변수임

b1_t block1 = ^{printf("%d", i);};return block1;

}

b1_t block1 = getBlock();block1(); // 1이 성공적으로 출력됨: 이 블록에 접근하면

// 블록은 지역변수를 캡쳐함

15.1.4 __block 변수

블록이 현재 범위에서 변수를 더 사용할 수 있다는 사실은 자명한 것으로 보인다. 블록은 단지사본을 만들었기 때문이다. 그런 이유로 혼란을 피하기 위해 여러분은 블록 내에 있는 변수는수정할 수 없다.그러나, __block속성을 가진고 사용된 변수의 경우 변경할 수 있다. 캡처된 변수는 블록이

호출될 때 여전히 접근 가능해야 하기 때문에, 더욱 미묘하다.extern 또는 static 변수는 auto 변수(C의 디폴트 변수 속성은 auto이다)가 아니기 때문에

블록으로 수정할 수 있음을 유의하기 바란다.

68

Page 69: C++에서 Objective-C까지 번역자료

//예제 1__block int i = 1;b1_t block1 = ^{printf("%d", i);};block1();// 1이 나타남

i = 2;block1();// 2가 나타남

//예제 2__block int i = 1;b1_t block1 = ^{printf("++i = %d", ++i);};block1();// 2가 나타남

block1();// 3이 나타남

//예제 3typedef void (^b1_t)(void);b1_t getBlock() //이 함수는 한 블록을 반환함

{__block int i = 1; // 이 변수는 함수내의 지역변수로

// 반환되는 블록은 getBlock() 외부에서는// 유효하지 않음

b1_t block1 = ^{printf("%d", i);};return block1;

}

b1_t block1 = getBlock();printf("Caution: the following call is invalid\n");block1(); // "i" 변수로 인한 세그멘테이션 폴트

69

Page 70: C++에서 Objective-C까지 번역자료

결론

이 문서는 C++의 개념과 비교하여 Objective-C를 여러 측면에서 간략하게 살펴볼 수 있도록하기 위해 작성하였다. 따라서 Objective-C 언어를 배우기 원하는 고급 개발자들을 위한 빠른레퍼런스로 유용할것이다. 이 문서를 통해 목표를 이루기 바라며, 또한 지속적인 개선을 위한독자의 피드백을 항상 환영한다.

70

Page 71: C++에서 Objective-C까지 번역자료

역자의 글

이 문서는 Pirre Chatelier가 Objective-C 언어를 배우기 원하는 C++ 개발자들을 위해서 작성

하게 되었으며, 프랑스어와 영어 버전으로 배포되었습니다. 역자 역시 iOS 환경에서 코코아 터치API를 배우기 시작했을때 겪었던 가장 큰 어려움이 바로 생소한 Objective-C 문법이었습니다.개발을 시작한지 1여년이 지나자 Objective-C가 그동안 개발언어로 사용해 왔던 C++, C#, Java에비해더욱배우기쉽고강력한언어라는점을알게되었으며,이러한점을한국의개발자들에게알리고자 저자의 양해를 구해 번역을 하게되었습니다. 원저자 Pirre Chatelier는 역자에게 아무런 조건없이 번역이 가능하도록 이 문서의 모든 소스들을 제공하였습니다. 저자의 이러한 열린마음에 다시 한번 감사드립니다.윈도우 시스템과 IE 환경에 지나치게 의존적인 한국의 환경에서 최근 Mac OS 사용자와 개

발자가 증가하고 있는 것은 매우 고무적인 일이며 긍정적인 현상으로 보입니다. 특히 Apple의앱스토어로 인한 독립 개발자의 창업과 개발열풍은 침체된 한국의 소프트웨어 시장을 활성화시

키는데 기여할 것으로 보입니다. 이 문서가 개발자들에게 작은 도움이 되었으면 합니다.번역내용에 대해서는 지속적인 업데이트를 진행할 예정이며, 이 문서의 개선을 위한 독자 여

러분의 피드백은 언제나 환영합니다. 문서의 개선을 위한 피드백은 [email protected]으로 주시기바라며,최신버전은역자의블로그(http://ivis.cwnu.ac.kr/tc/dongupak)를통해지속적으로배포할 예정입니다.

References[1] Apple Computer, Inc. Autozone. http://www.opensource.apple.com/darwinsource/10.5.

5/autozone-77.1/README.html.

[2] Apple Computer, Inc., Developer documentation. Garbage Collection Programming Guide.http://developer.apple.com/documentation/Cocoa/Conceptual/GarbageCollection.pdf.

[3] Apple Computer, Inc., Developer documentation. Key-Value Coding Programming Guide.http://developer.apple.com/documentation/Cocoa/Conceptual/KeyValueCoding/KeyValueCoding.pdf.

[4] Apple Computer, Inc., Developer documentation. The Objective-C 2.0 Programming Language.http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/ObjC.pdf.

[5] Boost. http://www.boost.org/.

[6] GNUstep. http://www.gnustep.org/.

[7] Aaron Hillegass. Cocoa Programming for MacOS X, 2nd edition. Addison-Wesley, 2004.

[8] Will Shipley. self = [supid init]. http://wilshipley.com/blog/2005/07/self-stupid-init.html.

71

Page 72: C++에서 Objective-C까지 번역자료

문서 개정내역

version 2.2

• 한국어 번역(김빛나,강영민,박동규), 사소한 수정

version 2.1

• 블록에 관한 설명 추가

version 2.0

• Objective-C 2.0 내용 추가

• 사소한 수정

version 1.11

• 타이포 수정

version 1.10

• Aaron Vegh에 의해 영어번역

• 클로닝에 관한 절을 재작성

• IMP 캐싱에 관한 절을 추가함

• initialize에 관한 자세한 해설.

• Objective-C++에 관한 자세한 해설.

• superclass에 관한 에러 수정

• “static” 인스턴스 데이터에 관한 자세한 설명

• 메소드 매개변수의 레이블에 관한 자세한 설명

• 가상 생성자에 대한 타이포 수정

• self와 super에 대한 실수 교정

• in, out, byref...에 대한 자세한 설명

• Objective-C 2.0에 대한 자세한 설명

• 정적 문자열에 대한 자세한 설명

• 사소한 타이포 수정

version 1.09

• 마이너한 변경

version 1.08

• 최초의 영어버전

72

Page 73: C++에서 Objective-C까지 번역자료

Index__block, 67.h, 8.m, 8.mm, 8, 66@class, 11@encode, 8@optional, 24@property, 59@protocol, 11, 23, 26@required, 24@synchronized, 53#import, 8#include, 8%@, 54ˆ , 66__strong, 50__weak, 50_cmd, 18NSMakeCollectable, 50Objective-C 2.0, 6Objective-C++, 8, 66Objective-C 키워드, 7

accessormutating, 45

alloc, 29, 40autorelease, 41, 43

pool, 42잘못된 사용, 42

AutoZone, 50

BOOL(형), 7bycopy, 26byref, 26

catch, 51const

메소드, 19, 55, 56copy, 36, 40copyWithZone, 36

delete, 40dynamic_cast, 21, 64

exceptions, 51catch, 51finally, 51throw, 51try, 51

finalize, 50finally, 51

Garbage collector, 49

__weak, 50NSMakeCollectable, 50

getter, 47

id, 7, 10, 65in, 26init, 29inout, 26

Key-value coding, 57prototypes, 58

KVC, 57prototypes, 58

memorycustom zones, 36

mutable키워드, 55, 56

mutableCopy, 38, 40mutableCopyWithZone, 38mutator, 45

new, 40Nil, 7, 10, 64nil, 7, 10, 20NSCopyObject, 36

Objective-C미래, 66

oneway, 26out, 26

private, 12상속, 22

protected, 12상속, 22

protocol, 23public, 12상속, 22

release, 40respondsToSelector, 64retain, 40retain cycle, 49RTTI, 64

SEL (type), 7self, 14setter

mutating, 45static, 13, 19strong, 50super, 14

this, 14

73

Page 74: C++에서 Objective-C까지 번역자료

throw, 19, 51try, 51

weak, 50

가비지 수집기, 49__strong, 50AutoZone, 50finalize, 50

가상

메소드, 22상속, 23생성자, 43순수 가상 메소드, 19, 23, 24

가상 메소드, 19가상성, 22개정

문서, 71개정내용

문서, 71객체, 10

치환가능, 치환불가능, 38객체 지향 모델, 10객체 지향 언어, 10

다중 쓰레딩, 53데이터 인스턴스, 10델리게이션, 20런타임

Objective-C, 65리테인 순환, 49매개변수

변수의 개수, 19익명, 19

메모리, 40alloc, 40autorelease, 41copy, 40delete, 40mutableCopy, 40new, 40release, 40retain, 40참조 횟수, 40치환가능, 치환 불가능 복사, 38

메소드, 8, 10const, 19static, 19가상, 22순수 가상, 19, 23, 24클래스 메소드, 13, 19현재 메소드(_cmd), 18

멤버함수에 대한 포인터, 17문서

개정, 71문자열, 54복사

연산자, 36치환가능, 치환불가능, 38

부모클래스, 64블록, 66

상속, 22public, protected, private, 22가상, 23다중, 22, 23단순, 22

생성자, 29가상, 35, 43디폴트, 33초기화 리스트, 35, 55, 56클래스 생성자, 35편리한 생성자, 43

생정자

지정, 33선택자, 17

respondsToSelector, 64SEL 형, 7멤버함수에 대한 포인터, 17

설정자, 45소멸자, 35속성, 10

static, 13쓰레드

다중 쓰레딩, 53안정성, 53

역사

Objective-C, 6오버로딩

메서드, 15연산자, 55, 56함수, 15

오토릴리즈

풀, 42인라인, 55, 56인자

디폴트 값, 19변수의 개수, 19익명, 19

인코딩, 54자격심사

in, out, inout, bycopy, byref, oneway, 26자동해제, 41전치 선언, 11접근자, 47

mutating, 45조사, 64주석, 7참조, 55, 56초기화, 29, 35

올바른 초기화, 30초기화 리스트, 35코코아, 6

74

Page 75: C++에서 Objective-C까지 번역자료

클래스, 10, 44, 64isKindOfClass, 64isMemberOfClass, 64NS 클래스, 8루트 클래스, 10

클래스 데이터, 13클래스 카테고리, 13, 22, 25, 27, 28템플릿, 55, 56파라미터

디폴트 값, 19변수의 개수, 19익명, 19

파일

.h, 8

.m, 8

.mm, 8구현, 8포함, 8헤더, 8

파일의 포함, 8포워딩, 20프렌드, 19, 55, 56프로토콜, 23, 28

@optional, 24@required, 24conformsToProtocol, 64공식, 23비공식, 25자격심사

in, out, inout, bycopy, byref, oneway, 26프로토타입, 13, 15

modifiers, 19프로퍼티, 59구현, 61속성, 60접근 문법, 62

하향 형변환, 21함수, 8형

BOOL, 7id, 7, 10SEL, 7

확장자

.mm, 66

75