Your SlideShare is downloading. ×
C++에서 Objective-C까지 번역자료
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Saving this for later?

Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime - even offline.

Text the download link to your phone

Standard text messaging rates apply

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

3,832
views

Published on

Pirre Cha

Pirre Cha


0 Comments
10 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
3,832
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
217
Comments
0
Likes
10
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. C++에서 Objective-C로 버전 2.3 ko Pierre Chatelier e-mail: pierre.chatelier@club-internet.fr 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 English Ce document est aussi disponible en français et en anglaisWith 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
  • 2. Contents목차 2개요 51 Objective-C와 코코아 6 1.1 Objective-C의 간단한 역사 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 1.2 Objective-C 2.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 문법 개요 7 2.1 키워드 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.2 주석 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.3 코드와 선언의 혼합 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.4 새로운 형식과 값 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.4.1 BOOL, YES, NO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.4.2 nil, Nil 과 id . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.4.3 SEL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.4.4 @encode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.5 소스 코드의 구성: .h와 .m 파일 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.6 클래스 이름에서 왜 NS를 사용하는가? . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.7 함수와 메소드의 차이 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 클래스와 객체 10 3.1 루트 클래스, id 형, nil 과 Nil 값 . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 3.2 클래스 선언 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 3.2.1 속성과 메소드 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 3.2.2 전치 선언 : @class, @protocol . . . . . . . . . . . . . . . . . . . . . . . . . 11 3.2.3 public, private, protected . . . . . . . . . . . . . . . . . . . . . . . . . . 12 3.2.4 static 속성 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 3.3 메소드 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 3.3.1 프로토타입과 호출, 인스턴스 메소드, 클래스 메소드 . . . . . . . . . . . . . 13 3.3.2 this, self, 그리고 super . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 3.3.3 메소드 안에서 인스턴스 변수 접근하기 . . . . . . . . . . . . . . . . . . . . 15 3.3.4 프로토타입 식별자와 시그너처, 오버로딩 . . . . . . . . . . . . . . . . . . . 15 3.3.5 멤버 함수에 대한 포인터: 선택자(Selector) . . . . . . . . . . . . . . . . . . 17 3.3.6 매개변수(parameter)의 디폴트 값 . . . . . . . . . . . . . . . . . . . . . . . 19 3.3.7 가변 개수의 인자 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.3.8 익명 인자 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.3.9 함수 원형(prototype) 한정어(modifier) - const, static, virtual, “= 0”, friend, throw . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.4 메세지와 전송 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 3.4.1 nil에게 메시지 보내기 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 3.4.2 알려지지 않은 객체에 메시지 위임 . . . . . . . . . . . . . . . . . . . . . . . 20 3.4.3 포워딩(forwarding): 알 수 없는 메시지 다루기 . . . . . . . . . . . . . . . . 20 3.4.4 하향 형변환(downcasting) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214 상속 22 4.1 단순 상속 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 4.2 다중 상속 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 4.3 가상성 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 4.3.1 가상 메소드 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 4.3.2 가상 메소드의 암묵적인 재정의 . . . . . . . . . . . . . . . . . . . . . . . . 22 4.3.3 가상 상속 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 4.4 프로토콜 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 4.4.1 공식 프로토콜 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 4.4.2 선택적 메소드 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 2
  • 3. 4.4.3 비공식 프로토콜 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 4.4.4 protocol 타입 객체 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 4.4.5 분리된 객체를 위한 메시지 자격심사 . . . . . . . . . . . . . . . . . . . . . 26 4.5 클래스 카테고리 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 4.6 프토토콜, 카테고리, 서브클래싱의 통합 사용 . . . . . . . . . . . . . . . . . . . . . 285 인스턴스화 29 5.1 생성자, 초기화 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 5.1.1 할당과 초기화의 차이 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 5.1.2 alloc과 init 사용하기 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 5.1.3 올바른 초기화의 예 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 5.1.4 self = [super init...] . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 5.1.5 초기화 실패 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 5.1.6 alloc+init으로 생성자를 “쪼개기” . . . . . . . . . . . . . . . . . . . . . . 33 5.1.7 디폴트 생성자 : 초기화 지정 . . . . . . . . . . . . . . . . . . . . . . . . . . 33 5.1.8 초기화 리스트와 인스턴스 데이터의 디폴드 값 . . . . . . . . . . . . . . . . 35 5.1.9 가상 생성자 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 5.1.10 클래스 생성자 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 5.2 소멸자 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 5.3 복사 연산자 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 5.3.1 고전적인 복제방식, copy, copyWithZone:, NSCopyObject() . . . . . . . . . 36 5.3.2 NSCopyObject() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 5.3.3 더미 복제, 치환성, mutableCopy 와 mutableCopyWithZone: . . . . . . . . 386 메모리 관리 40 6.1 new 와 delete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 6.2 참조 횟수 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 6.3 alloc, copy, mutableCopy, retain, release . . . . . . . . . . . . . . . . . . . . . 40 6.4 자동 해제 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 6.4.1 autorelease의 중요성 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 6.4.2 오토릴리즈 풀(autorelease pool) . . . . . . . . . . . . . . . . . . . . . . . . 42 6.4.3 다수의 오토릴리즈 풀 사용하기 . . . . . . . . . . . . . . . . . . . . . . . . 42 6.4.4 autorelease에서의 주의사항 . . . . . . . . . . . . . . . . . . . . . . . . . . 42 6.4.5 autorelease와 retain . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 6.4.6 편리한 생성자, 가상 생성자 . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 6.4.7 설정자 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 6.4.8 접근자(Getters) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 6.5 리테인 순환(retain cycles) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 6.6 가비지 수집기 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 6.6.1 finalize . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 6.6.2 weak, strong . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 6.6.3 NSMakeCollectable() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 6.6.4 AutoZone . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 507 예외 처리 518 다중 쓰레딩 53 8.1 쓰레드-세이프티 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 8.2 @synchronized . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 539 Objective-C 문자열 54 9.1 Objective-C의 유일한 정적객체 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 9.2 NSString과 인코딩 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 9.3 객체의 설명, %@ 포맷 확장, NSString을 C 문자열로 . . . . . . . . . . . . . . . . . . 54 3
  • 4. 10 C++ 고유의 특징 55 10.1 참조 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 10.2 인라인 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 10.3 템플릿 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 10.4 연산자 오버로딩 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 10.5 프렌드 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 10.6 const 메소드 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 10.7 생성자에서 초기화 리스트 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5511 STL과 코코아 56 11.1 컨테이너 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 11.2 반복자(Iterator) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 11.2.1 고전적인 열거 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 11.2.2 빠른 나열형 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 11.3 함수자 (함수 객체) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 11.3.1 셀렉터 사용하기 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 11.3.2 IMP 캐싱 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 11.4 알고리즘 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5712 Implicit code 58 12.1 Key-value 코딩 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 12.1.1 원리 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 12.1.2 차단 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 12.1.3 프로토타입 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 12.1.4 고급 기능 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 12.2 프로퍼티 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 12.2.1 프로퍼티의 활용 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 12.2.2 프로퍼티의 서술 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 12.2.3 프로퍼티 속성 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 12.2.4 프로퍼티의 사용자 지정 구현 . . . . . . . . . . . . . . . . . . . . . . . . . . 63 12.2.5 프로퍼티에 접근하는 문법 . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 12.2.6 고급 내용 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6413 동적성질 65 13.1 RTTI (런타임 형 정보) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 13.1.1 class, superclass, isMemberOfClass, isKindOfClass . . . . . . . . . . . 65 13.1.2 conformsToProtocol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 13.1.3 respondsToSelector, instancesRespondToSelector . . . . . . . . . . . . 65 13.1.4 id형을 가진 강한 타이핑과 약한 타이핑 . . . . . . . . . . . . . . . . . . . . 66 13.2 런타임시 Objective-C 클래스의 조작 . . . . . . . . . . . . . . . . . . . . . . . . . . 6614 Objective-C++ 6715 Objective-C의 미래 67 15.1 블록(block) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 15.1.1 지원과 사용사례 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 15.1.2 구문 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 15.1.3 환경의 캡쳐링 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 15.1.4 __block 변수 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68결론 70참고문헌 71문서 개정내역 72색인 73 4
  • 5. 소개 이 문서는 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
  • 6. 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 1980 1983 1989 1995 1998-99 2001 2005-07 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♯ C♯2 (C♯3 to come) 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
  • 7. 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, NOC++에서 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)의 인스턴스이다.12.4.3 SELSEL 타입은 어떤 클래스 인스턴스 객체와도 무관한 메소드 식별자를 뜻하는 실렉터(selector)를저장할 수 있디. 이 값은 @selector에 대한 호출을 통해서 얻을 수 있다. 실렉터는 기술적으로는 실제 함수 포인터가 아니지만, C언어에서 사용하는 함수(C++의 메소드)에 대한 포인터의일종으로 생각할 수 있다. 자세한 내용은 17쪽 3.3.5절을 참조하라. 1 이와 달리 C++의 클래스는 객체를 만드는 설계도로 설명할 수 있다. [譯註] 7
  • 8. 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 파일 // Foo.cpp 파일 #ifndef __FOO_H__ //이중 포함을 방지 #include "Foo.h" #define __FOO_H__ // ... class Foo { ... }; #endif Objective-C //Foo.h 파일 // Foo.m 구현 파일 //클래스 선언은 Java 키워드인 #import "Foo.h" //"interface"와 의미가 //다르니 혼동하지 말것 @implementation Foo @interface Foo : NSObject ... { @end ... } @end2.6 클래스 이름에서 왜 NS를 사용하는가?이 문서에 나타나는 거의 모든 클래스 이름은 NSObject 또는 NSString처럼 NS로 시작한다. 이유는 간단하다: 이 클래스들은 모두 코코아 클래스이고, 대부분의 코코아 클래스는 NeXTStep환경에서 처음 만들어졌기 때문에 NS로 시작한다. 프로그램 작성시 클래스가 어디서 온 것인지를 식별하기 위해 접두어를 사용하는 것이 일반적인 관행이다3 .2.7 함수와 메소드의 차이Objective-C는 단순히 “대괄호(꺽쇠)를 사용하여 함수를 호출”하는 언어가 아니다. 이런 생각은아래의 코드 2 #ifndef ... 와 같은 매크로를 사용한다 [譯註] 들어 게임개발에 널리 사용되는 cocos2d 클래스들은 CC를 접두어로 사용한다. 그러나 C++와 C#의 경우는 3 예를네임스페이스를 통하여 클래스 이름이 충돌나는 것을 방지한다 [譯註] 8
  • 9. object.doSomething();대신에[object doSomething];의 형태로 작성된 코드를 보고 있으면 쉽게 떠올릴 수 있는 생각이다. 그러나 사실 Objective-C는 C의 상위집합이기 때문에 함수를 선언하고 구현하며 호출하는 문법은 C와 완전히 동일하다. 반면,C에서 존재하지 않는 메소드는 대괄호([ ])를 사용하는 특별한 구문법을 가지고 있다. 그런데,이러한 차이는 문법적으로 다른 것에 그치는 것이 아니라, 의미에 있어서도 차이를 갖는다. 자세한내용은 10쪽 3.2절에서 더 깊이 다룰 것이다: 이것은 메소드의 호출이 아니라 메시지를 보내는것이다. 이것은 그냥 단순히 학술적인 구분이 아니라 Objective-C의 메커니즘이 어떠한지를 드러내는 특징이다. 소스 코드 작성에 있어서는 별 차이가 없지만, 이 메커니즘은 더욱 동적인 특성을갖는다. 예를 들면, Objective-C는 실행중에 메소드를 추가하는 일도 가능하다 (비고. 66쪽 13.2절). 이런 구문은 읽기도 쉬운데, 특히 중첩된 호출문의 경우에 더욱 좋다 (비고. 13쪽 3.3.1절). 9
  • 10. 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)라 부른다. 4 C++는 friend 함수를 사용하여 객체 내부의 멤버에 손쉽게 접근하는 편의성을 제공하기도 하며, C 언어와의 호환성을 유지하기 위하여 함수나 메소드를 문법적으로 동일한 방식으로 호출하여 C 개발자들이 쉽게 C++ 개발자가 될수 있도록 하였다. 이러한 방식에 장점도 분명 있겠지만 객체지향적 개념을 훼손하는 C 언어의 특성을 배제하지 못하여다양한 문제를 일으키기도 한다. 또한 C++는 간략한 원칙보다는 다양한 상황에 대처하는 ad hoc한 문법들이 복잡하게포함되어 학습이나 활용이 매우 어려운 언어이다. [譯註] 5 Run-Time Type Information(런타임 형 정보)의 약자 [譯註] 6 런타임 시스템은 어떤 컴퓨터 언어로 작성된 프로그램이 실행되는 것을 지원하기 위해 설계된 소프트웨어를 뜻한다.런타임 시스템은 기본적인 저수준 명령어의 구현을 담고 있으며, 고수준 명령어들을 구현하고 형 검사(type checking),디버깅, 코드 생성, 최적화 등의 기능을 지원할 수도 있다. [譯註] 7 약한 형 결합 방식은 프로그래밍 언어에서 강한 형 결합(strong typing)의 반대 개념으로 사용되었는데, 데이터의 형변환을 컴파일러나 인터프리터가 내부적으로 수행하는 방식이다. 이 방식은 컴파일 타임에 에러를 검출하지 않기 때문에프로그램에는 좀 더 많은 주의가 필요하지만 객체의 동적인 생성과 사용에 매우 편리한 환경을 제공한다. [譯註] 10
  • 11. C++ Objective-C class Foo @interface Foo : NSObject { { double x; double x; } public: int f(int x); -(int) f:(int)x; float g(int x, int y); -(float) g:(int)x :(int)y; }; @end @implementation Foo -(int) f:(int)x {...} int Foo::f(int x) {...} -(float) g:(int)x :(int)y {...} float Foo::g(int x, int y) {...} @endC++에서는 속성과 메소드가 클래스의 중괄호(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
  • 12. C++ // Foo.h 파일 // Foo.cpp 파일 #ifndef __FOO_H__ #include "Foo.h" #define __FOO_H__ #include "Bar.h" class Bar; //전치 선언 void Foo::useBar(void) class Foo { { ... Bar* bar; } public: void useBar(void); }; #endif Objective-C // Foo.h 파일 // Foo.m 파일 @class Bar; // 전치 선언 #import "Foo.h" @interface Foo : NSObject #import "Bar.h" { Bar* bar; @implementation Foo } -(void) useBar -(void) useBar; { ... @end } @end3.2.3 public, private, protected객체 지향 모델의 주요한 특징 중에 하나로 데이터 캡슐화(encapsulation)이 있다. 이것은 코드의어떤 부분에서는 해당 데이터가 보이지 않도록 하는 것인데, 이렇게 함으로써 데이터가 훼손되는것을 막는다.9 9 이러한 캡슐화를 통해 객체 내부의 데이터를 안전하게 보호할 수 있으며, 이렇게 보호된 데이터가 훼손되지 않은값을 유지하고 있는 상태를 데이터의 ‘완결성(integrity)’라고 한다. 코드가 특정 데이터를 접근할 수 있는지 그렇지 아니한지를 그 데이터를 ‘볼 수 있다’ 혹은 ‘볼 수 없다’로 표현한다. 이러한 데이터 접근성을 ‘가시성(visibility)’라고도부른다. 데이터의 완결성을 유지할 때, 오류도 쉽게 찾을 수 있다. [譯註] 12
  • 13. C++ Objective-C class Foo @interface Foo : NSObject { { public: @public int x; int x; int apple(); @protected: protected: int y; int y; int pear(); @private: int z; private: } int z; int banana(); -(int) apple; }; -(int) pear; -(int) banana; @endC++에서 속성과 메소드는 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)은 괄호로 묶어서 표시한다. 10 C++는 메소드나 인스턴스 데이터 모두 public, protected, private가 될 수 있다. [譯註] 13
  • 14. • 매개변수(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, 그리고 superObjective-C에는 메시지를 전달할 특별한 대상으로 self와 super가 존재한다. self는 현재 객체(C++에서의 this처럼)이며, super는 부모 클래스이다. this 키워드는 Objective-C에서 존재하지않으며, self로 대치되었다. 주의 : 사실 self는 키워드가 아니라, 모든 메소드에게 전달되는 숨겨진 매개변수(parameter)인데, 그 값이 현재 객체이다. self는 C++의 this 키워드와는 달리 그 값을 바꿀 수도 있다. 하지만이런 변경은 생성자에서만 쓸모가 있다(비고. 29쪽 5.1절). 14
  • 15. 3.3.3 메소드 안에서 인스턴스 변수 접근하기C++에서와 같이, Objective-C 메소드는 현재 객체의 인스턴스 변수를 액세스할 수 있다. C++에서필요에 따라 사용할 수 있는 this->는 Objective-C에서는 self->로 쓴다. C++ Objective-C class Foo @interface Foo : NSObject { { int x; int x; int y; int y; void f(void); } }; -(void) f; @end void Foo::f(void) { @implementation Foo x = 1; -(void) f int y; //this->y와 구분이 모호함 { y = 2; //지역변수 y를 사용 x = 1; this->y = 3; //현재 객체의 y속성임 int y; //this->y와 구분이 모호함 } y = 2; //지역변수 y를 사용 self->y = 3; //현재 객체의 y속성임 } @end3.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
  • 16. Objective-C에서는 모든 함수가 C 함수이기 때문에 오버로드될 수 없다. 오버로딩이 가능하려면 gcc에서 처럼 컴파일러에게 C9911 표준을 사용하라고 지정할 수 있어야 한다. 그러나, 메소드는C 함수와 다른 문법을 사용하고 있으며, 파라미터 레이블을 이용하여 구별할 수 있다. Objective-C int 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절에서 이뤄진다. 11 ANSI표준화 이후 크게 진전되지 않던 C의 표준 개정 작업이 1990년대에 활발히 진행되었는데, 90년대 후반의 개정작업들이 ISO/IEC 9899:1999에 정리되어 출간되었다. 이 표준을 따르는 C 규범을 C99라 부른다. [譯註] 12 함수 포인터를 부연 설명하자면 함수 포인터의 자료형(type)은 당연히 포인터 형이다. 함수 포인터가 일반적인 포인터와 다른 점은 변수의 주소 값이 아니라 함수의 주소 값을 저장한다는 것 뿐이다. 함수는 사실상 코드의 한 부분인데,여기서 코드라는 것은 프로그래머가 프로그래밍 언어를 이용하여 작성한 프로그램이 컴파일(compile)되어 메모리에 탑재될 수 있는 기계어 형태로 변환된 것을 의미한다. 함수가 시작되는 부분에 대응하는 코드가 메모리 공간의 어느 곳에자리를 잡고 있는지 알려주는 주소값을 저장하는 것이 바로 함수 포인터이다. C 언어는 함수 자체를 변수로 만들 수 없기때문에 함수 포인터를 통하여 포인터가 가리키는 곳의 함수를 실행시킬 수 있다. [譯註] 16
  • 17. 3.3.5 멤버 함수에 대한 포인터: 선택자(Selector)Objective-C에서 메소드는 괄호와 레이블을 사용하는 독특한 문법을 갖는다. 이 문법으로 (C 언어의 표준적인) 함수를 선언하는 것은 불가능하다. 함수에 대한 포인터의 개념은 C와 Objective-C가모두 동일하지만, 메소드에 대한 포인터에 대해서는 두 언어에 차이가 있다.C++에서 메소드에 대한 포인터를 쓰기 위한 구문법(syntax)은 까다롭기는 하지만, C 언어의함수 포인터와 동일하다. 포인터 구문의 초점이 자료형(type)에 맞춰져 있다. C++ class Foo { public: int f(float x) {...} }; Foo bar int (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
  • 18. 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
  • 19. 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]; //위의 문장과 동일 } @end3.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, throwC++에서 함수 원형(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에서는 불가능하다. 14 C++는 디폴트 파라미터를 지정하여 매개변수가 없을 경우 디폴트 매개변수와 그 매개변수의 값을 자동으로 생성할수 있는 편의성을 제공하지만, 이것이 원치않는 값을 설정하여 찾기 힘든 버그를 생성할 가능성도 있다. [譯註] 19
  • 20. 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
  • 21. -(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"];//경고 없음 16 NSUndoManager 클래스는 실행취소(undo)와 재실행(redo) 연산을 위해 만들어진 범용 레코더이다. 17 하향 형변환을 이렇게 간단하게 할 수 있는 동적인 특성은 Objective-C가 가지는 큰 장점이다. 이러한 특성 때문에Objective-C에서는 모든 객체를 간단히 최상위 클래스 포인터인 id형으로 다룰 수 있다. [譯註] 21
  • 22. 4 상속4.1 단순 상속Objective-C는 상속 개념을 잘 구현하고 있으나, 모호성 문제를 유발하는 다중 상속을 지원하지는 않는다(사실 Java, C# 등의 객체지향언어들도 이 문제 때문에 다중 상속을 지원하지 않고interface라는 방식을 사용한다). 이 때문에 발생하는 제약사항은 이 문서의 뒷부분에 설명하는다른 방법인 프로토콜, 클래스 카테고리등으로 대치한다(23쪽 4.4절, 27쪽 4.5절). C++ Objective-C class Foo : public Bar, @interface Foo : Bar //단일 부모를 통한 protected Wiz //상속만을 허용함 { //Wiz라는 부모의 기능에 대한 상속이 필요함. } //따라서 이 한계를 대체하는 기법이 필요하다 { } @endC++에서 클래스는 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
  • 23. 순수 가상 메소드의 개념(하위 클래스에서 반드시 재정의해야 하는 메소드)은 공식 프로토콜의개념을 통해 해결하였다(비고. 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
  • 24. 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
  • 25. @protocol Slave @required //필수 부분 -(void) makeCoffee; -(void) duplicateDocument:(Document*)document count:(int)count; @optional //선택 부분 -(void) sweep; @required //필수/선택 절을 구분할 수 있다 -(void) bringCoffee; @end4.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
  • 26. 엄밀히 말하면, 프로토타입에 관한 지식과는 별도로 비공식 프로토콜은 컴파일러에게는 쓸모가 없으며 이것이 객체의 사용을 제한하지 않는다. 그러나, 이 방법은 코드의 자체 문서화를통해서 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
  • 27. 4.5 클래스 카테고리클래스에 대한 카테고리는 클래스의 구현부를 여러 부분으로 나눌때 사용하는 방법이다. 즉 매우큰 클래스의 개발할 경우에 여러명의 개발자가 클래스의 구현을 나누어하거나, 이미 개발된 클래스를 상속하지 않고도 새로운 기능을 추가하는 Objective-C의 매우 독특하고도 편리한 기능이다.이때 각 카테고리는 그 클래스의 일부이다. 클래스는 카테고리를 임의의 갯수만큼 사용할 수 있으나, 그 어느 것도 인스턴스 데이터는 추가할 수 없다. 클래스 카테고리는 다음과 같은 장점을제공한다. • 세심한 개발자를 위해, 메소드의 그룹을 만드는 것이 가능한다. 아주 다채로운 클래스를 위해, 다른 역할을 하는 코드를 완전히 분리할 수 있다. • 한 클래스에 대하여 협업 작업을 하는 경우 별도 컴파일이 가능하다. • 카테고리의 인터페이스와 그것의 구현 파일이 존재하는 경우(.m 파일), 파일 내부에 표시되 는 private 메소드를 정의하는 것이 아주 쉽다(비록 외부에서 아무 객체나 메소드의 프로토 타입을 알고있어 사용할 수 있고, 호출 제한이 없어도). 이 카테고리를 위한 적당한 이름은 FooPrivateAPI 가 될 수 있다. • 클래스내의 동일한 코드를 복제하지 않고 별도의 응용프로그램에서 확장시킬 수 있다. 따 라서 어떤 클래스도 기존의 코코아 클래스를 확장시킬 수 있다. 마지막 포인트는 아주 중요하다. 모든 개발자는 개발을 위해 필요한 메소드로 표준 클래스를확장할 수 있는 선택권을 가진다. 하지만 상속 역시 클래스를 확장하는 대표적인 방법이다. 그러나, 간단한 기능만을 상속하는 컨텐츠의 경우를 생각해보면 간단한 기능을 위해서 무거운 하위클래스 구조를 만드는 것이 바람직하지 않을 것이다. 더욱이, 단 하나의 메소드만을 확장하기 위해서 그 클래스의 하위 클래스를 만드는 것은 불필요한 노력으로 볼 수 있다. 클래스 카테고리는이 문제를 위한 멋지고 우아한 해결방법이다. C++ Objective-C class MyString : public string @interface NSString (VowelsCounting) { //{}을 사용하지 않았다는 것에 유의 public: -(int) vowelCount; //모음의 갯수를 계산 //모음의 갯수를 계산 @end int vowelCount(void); }; @implementation NSString (VowelsCounting) -(int) vowelCount int MyString::vowelCount(void) { { ... ... } } @endC++에서는 새로운 클래스는 제한없이 사용할 수 있다.Objective-C에서 (표준 코코아 클래스인)NSString 클래스는 전체 프로그램 안에서 사용할 수있는 확장 기능을 갖게 된다. 이때 새 클래스는 만들어지지 않는다. 모든 NSString 객체는 확장기능에서 혜택을 받는다.(심지어 문자열 상수라 할지라도 기능의 혜택을 받는다, 비고. 54쪽 9.1절) 하지만 인스턴스 변수는 카테고리 안에 추가될 수 없기 때문에 {. . . } 블록이 없다. 카테고리도 “사적인 사용”을 위해 완벽히 익명으로 될 수 있다. 27
  • 28. @interface NSString () //{}을 사용하지 않았다는 것에 유의 -(int) myPrivateMethod; @end @implementation NSString () -(int) myPrivateMethod { ... } @end4.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
  • 29. 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-C Foo* 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
  • 30. 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
  • 31. 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
  • 32. 따라서, 치환을 실행하는 클래스를 개발하는 일이 있다면, 여러분은 그것을 초기화하는 동안할당된 객체를 최소화하는 방법을 알아야 한다. 이 문제는 오류 처리와 동일한다. 우리는 생성을실패(잘못된 파라미터, 이용할 수 없는 리소스..)한 경우에 무엇을 해야할까? 이것은 섹션 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
  • 33. 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
  • 34. -(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
  • 35. 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
  • 36. 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
  • 37. //만일 부모 클래스가 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
  • 38. //만일 부모클래스가 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
  • 39. 이를 위하여 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
  • 40. 6 메모리 관리6.1 new 와 deleteC++ 키워드인 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 기능과 동등하지 않다. 반대로 부스트 라이브러리(boostlibrary) [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
  • 41. 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
  • 42. -(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
  • 43. • 어떤 release 메시지든지 autorelease로 대체될 수 있다는 것이 사실이라 하더라도, autorelease 는 성능면에서 release보다 못하다. 이것은 오토릴리즈 풀이 정상 release보다 더 많은 작 업을 하기 때문이다. 더욱이, 모든 메모리 해지를 지연시키는것은 불필요하게 메모리 낭비를 초래할 수 있다.6.4.5 autorelease와 retainautorelease 덕에, 메소드는 스스로 해제될 수 있는 객체를 만들 수 있다. 그러나, 객체가 더 오래유지하려 하는 것이 일반적이다. 이런 경우에, 객체가 자기에게 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
  • 44. //나쁜 편리한 생성자 +(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
  • 45. 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
  • 46. 할당 (불완전한 코드)가장 간단한 경우로, 이전의 참조값을 덮어쓰는 방법이 있다.-(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
  • 47. //올바른 코드 //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
  • 48. @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
  • 49. @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가 아님) //객체가 반환됨 } @end6.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
  • 50. 직접하지 않기때문에 메모리의 활용을 최적화시킬 수 없으며, 이로인해 리소스의 낭비가 심해질가능성이 높다. 따라서 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
  • 51. 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{@try{ dangerousAction(); dangerousAction(); } problem = NO; @catch (MyException* e){} doSomething();@catch (MyException* e){ } doSomething(); @catch (NSException* e){ cleanup(); doSomethingElse();} @throw //여기서 예외가 다시 던져진다@catch (NSException* e){ } doSomethingElse(); @finally{ cleanup(); cleanup(); //여기에서 예외가 다시 던져진다 } @throw}if (!problem) cleanup(); 엄밀히 말하면, @finally는 필수는 아니지만, 예외를 제대로 처리하기 위한 유용한 기법이다.앞의 예제에서 살펴본 것처럼 @catch가 다시 예외를 발생시킨 경우도 잘 처리한다. 사실, @finally는 @try 범위를 벗어나면 실행되는데, 다음에 이에 관한 설명이 이어진다. 51
  • 52. int f(void) { printf("f: 1-you see men"); //"@"구문을 이해하기 위하여 문자열에 관한 절을 참조하라 @throw [NSException exceptionWithName:@"panic" reason:@"you don’t really want to known" userInfo:nil]; printf("f: 2-you never see men"); } int g(void) { printf("g: 1-you see men"); @try { f(); printf("g: 2-you do not see me (in this example)n"); } @catch(NSException* e) { printf("g: 3-you see men"); @throw; printf("g: 4-you never see men"); } @finally { printf("g: 5-you see men"); } 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
  • 53. 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 20 POSIX는 Portable Operating System Interface [for Unix]의 약자로 서로 다른 UNIX 운영체지의 공통 API를 정리하여 이식성이 높은 유닉스 응용 프로그램을 개발하기 위한 목적으로 IEEE가 정한 애플리케이션 인터페이스 사양이다.이 사양에는 C 언어 인터페이스인 시스템 호출과 프로세스 환경, 파일과 디렉토리, 시스템 데이터베이스등 다양한 분야를포함하고 있다. 21 뮤텍스(mutual exclusion, Mutex)는 동시 프로그래밍에서 공유 불가능한 자원의 동시 사용을 피하기 위해 사용되는알고리즘으로, 임계 구역(critical section)으로 불리는 코드 영역에 의해 구현된다. 53
  • 54. 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
  • 55. 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
  • 56. 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
  • 57. 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
  • 58. 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
  • 59. 이러한 구문이 지원되기 때문에, 한 클래스의 인스턴스 데이터를 위해 같은 이름을 사용하는다른 클래스의 일부 객체를 처리하기 위해 동일한 코드를 사용하는 것이 가능해졌다. 위의 예에서s5의 경우는 에러가 나는데 myA.foo와 같이 b 객체의 멤버인 myA 내부에 있는 속성값을 얻기위해서는 valueForKeyPath:를 사용하여야 한다. 이와 같은 방법으로 내부의 속성값에 손쉽게접근하는 기법은 Objective-C가 이에 관한 런타임 엔진을 가진 매우 동적인 언어이기 때문이다. 데이터와 일부 함수를(특히 메소드 호출) 바인딩시키는 최상의 사용 예는 여기 자세히 다루지는 않았지만 Key-Value Observing(KVO)과 같은 몇 가지 방식이 있다.2312.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
  • 60. 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-safe 24 (비고. 53쪽 8.1절) 해야 하는가? 24 스레드 안전(thread-safe)은 멀티 스레드 프로그래밍에서 일반적으로 어떤 함수나 변수, 혹은 객체가 여러 스레드로부터 동시에 접근이 이루어져도 프로그램의 실행에 문제가 없음을 뜻한다. 보다 엄밀하게는 하나의 함수가 한 스레드로부터 60
  • 61. • 접근자(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
  • 62. @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 { ... } @end12.2.3 프로퍼티 속성프로퍼티는 다음의 템플릿 형태로 선언한다.@property 형 이름;또는@property (속성) 형 이름; 이 템플릿이 주어지지 않은 경우 속성은 디폴트 값을 가지며, 템플릿이 주어지면 속성은 이전절에서 언급한 질문에 대한 답변을 재정의 할 수 있다. 이 형태는 다음과 같다 : • readwrite (디폴트 속성) 또는 readonly는 프로퍼티가 getter/setter 둘 다 또는 getter만을 가져야 한다는 것을 말한다. • assign(디폴트), retain, copy는 값이 내부에 어떻게 저장되는지 알려준다. • thread-safety가 발생하는 것을 방지하기 위해 nonatomic이 생성된다. nonatomic은 디폴트 로 생성된다.(atomic 키워드는 없다); • getter=..., setter=... 는 접근자의 디폴트 이름을 변경한다. 62
  • 63. 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"에 바인드 된다 //(여기서는 존재하지 않은 속성임) @end12.2.5 프로퍼티에 접근하는 문법프로퍼티의 값을 얻거나(get), 지정(set)하려면 dot 문법을 사용한다. 이는 간단한 C struct와 같은문법이고, keypath 원칙과도 일관성이 있다(비고. 58쪽 12.1.1절). 이 경우의 성능은 기본 메소드에대한 직접 호출과 동일한다. 63
  • 64. @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 { @public int 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
  • 65. 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, instancesRespondToSelectorrespondsToSelector:는 NSObject로부터 상속된 인스턴스 메소드이다. respondsToSelector:를통해 객체는 주어진 메소드가 구현되었는가 여부를 확인할 수 있다. 이를 위하여 셀렉터 개념을사용한다(비고. 15쪽 3.3.4절). 예를 들어 다음과 같은 문장을 살펴보자. if ( [self respondsToSelector:@selector(work)] ) { printf("나는 부지런해.n"); [self work]; } 65
  • 66. 상속 메소드의 검사없이 클래스가 주어진 메소드를 구현하였는지 알기위해서는, 클래스 메소드인 instancesRespondToSelector:를 사용할 수 있다. if ([[self class] instancesRespondToSelector:@selector(findWork)]) { printf("I can find a job without the help of my mothern"); }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]; else printf("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
  • 67. 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에서 구현한 것이다. 쉽게 말하면, 클로저는 실행시에 곧바로 환경(변수 값)을 캡쳐하는 익명의 함수로 볼수 있다. 블록은 콜백함수를 대치하는 매우 유용한 개념이며, 좀 더 일반적으로 말하자면 지연되거나또는 비동기적 함수 호출을 위해 반드시 작성되어야 하는 코드를 줄이는데도 유용하다. 25 http://developer.apple.com/releasenotes/Cocoa/RN-ObjectiveC/index.html 26 GCC 컴파일러는 2010년 12월 현재 4.5.2버전까지 개발되었으며, C, C++, Objective-C, Fortran, Java, Ada, Go언어를 지원한다. http://gcc.gnu.org/ 27 LLVM은 Low Level Virtual Machine(저수준 가상 기계)의 약자로 컴파일, 링크, 런타임 등 각 시점에서 프로그램을최적화하도록 설계된 모든 프로그래밍 언어에 대응 가능한 컴파일러 하부구조(infrastructore)이다. http://llvm.org/ 67
  • 68. 15.1.2 구문블록은 다음과 같은 구문에 의해 생성된다.^{...some code...}또는^(parameter1,[other parameters...]){...some code...} 블록은 꺽쇠(ˆ)를 별표(*)로 대체하는 상황에서 함수 포인트와 같은 변수내에 저장될 수 있다.typedef void (^b1_t)(void);b1_t block1 = ^{printf("hellon");};block1(); // hello를 출력함typedef void (^b2_t)(int i);b2_t block2 = ^(int i){printf("%dn", 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
  • 69. //예제 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 invalidn");block1(); // "i" 변수로 인한 세그멘테이션 폴트 69
  • 70. 결론 이 문서는 C++의 개념과 비교하여 Objective-C를 여러 측면에서 간략하게 살펴볼 수 있도록하기 위해 작성하였다. 따라서 Objective-C 언어를 배우기 원하는 고급 개발자들을 위한 빠른레퍼런스로 유용할것이다. 이 문서를 통해 목표를 이루기 바라며, 또한 지속적인 개선을 위한독자의 피드백을 항상 환영한다. 70
  • 71. 역자의 글 이 문서는 Pirre Chatelier가 Objective-C 언어를 배우기 원하는 C++ 개발자들을 위해서 작성하게 되었으며, 프랑스어와 영어 버전으로 배포되었습니다. 역자 역시 iOS 환경에서 코코아 터치API를 배우기 시작했을때 겪었던 가장 큰 어려움이 바로 생소한 Objective-C 문법이었습니다.개발을 시작한지 1여년이 지나자 Objective-C가 그동안 개발언어로 사용해 왔던 C++, C#, Java에 비해 더욱 배우기 쉽고 강력한 언어라는 점을 알게되었으며, 이러한 점을 한국의 개발자들에게알리고자 저자의 양해를 구해 번역을 하게되었습니다. 원저자 Pirre Chatelier는 역자에게 아무런 조건없이 번역이 가능하도록 이 문서의 모든 소스들을 제공하였습니다. 저자의 이러한 열린마음에 다시 한번 감사드립니다. 윈도우 시스템과 IE 환경에 지나치게 의존적인 한국의 환경에서 최근 Mac OS 사용자와 개발자가 증가하고 있는 것은 매우 고무적인 일이며 긍정적인 현상으로 보입니다. 특히 Apple의앱스토어로 인한 독립 개발자의 창업과 개발열풍은 침체된 한국의 소프트웨어 시장을 활성화시키는데 기여할 것으로 보입니다. 이 문서가 개발자들에게 작은 도움이 되었으면 합니다. 번역내용에 대해서는 지속적인 업데이트를 진행할 예정이며, 이 문서의 개선을 위한 독자 여러분의 피드백은 언제나 환영합니다. 문서의 개선을 위한 피드백은 dongupak@gmail.com으로 주시기 바라며, 최신 버전은 역자의 블로그(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
  • 72. 문서 개정내역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
  • 73. Index__block, 67 __weak, 50.h, 8 NSMakeCollectable, 50.m, 8 getter, 47.mm, 8, 66@class, 11 id, 7, 10, 65@encode, 8 in, 26@optional, 24 init, 29@property, 59 inout, 26@protocol, 11, 23, 26@required, 24 Key-value coding, 57@synchronized, 53 prototypes, 58#import, 8 KVC, 57#include, 8 prototypes, 58%@, 54 memoryˆ , 66 custom zones, 36__strong, 50 mutable__weak, 50 키워드, 55, 56_cmd, 18 mutableCopy, 38, 40NSMakeCollectable, 50 mutableCopyWithZone, 38Objective-C 2.0, 6 mutator, 45Objective-C++, 8, 66Objective-C 키워드, 7 new, 40 Nil, 7, 10, 64accessor nil, 7, 10, 20 mutating, 45 NSCopyObject, 36alloc, 29, 40autorelease, 41, 43 Objective-C pool, 42 미래, 66 잘못된 사용, 42 oneway, 26AutoZone, 50 out, 26BOOL(형), 7 private, 12bycopy, 26 상속, 22byref, 26 protected, 12 상속, 22catch, 51 protocol, 23const public, 12 메소드, 19, 55, 56 상속, 22copy, 36, 40copyWithZone, 36 release, 40 respondsToSelector, 64delete, 40 retain, 40dynamic_cast, 21, 64 retain cycle, 49exceptions, 51 RTTI, 64 catch, 51 SEL (type), 7 finally, 51 self, 14 throw, 51 setter try, 51 mutating, 45finalize, 50 static, 13, 19finally, 51 strong, 50 super, 14Garbage collector, 49 this, 14 73
  • 74. throw, 19, 51 연산자, 36try, 51 치환가능, 치환불가능, 38 부모클래스, 64weak, 50 블록, 66가비지 수집기, 49 상속, 22 __strong, 50 public, protected, private, 22 AutoZone, 50 가상, 23 finalize, 50 다중, 22, 23가상 단순, 22 메소드, 22 생성자, 29 상속, 23 가상, 35, 43 생성자, 43 디폴트, 33 순수 가상 메소드, 19, 23, 24 초기화 리스트, 35, 55, 56가상 메소드, 19 클래스 생성자, 35가상성, 22 편리한 생성자, 43개정 생정자 문서, 71 지정, 33개정내용 선택자, 17 문서, 71 respondsToSelector, 64객체, 10 SEL 형, 7 치환가능, 치환불가능, 38 멤버함수에 대한 포인터, 17객체 지향 모델, 10 설정자, 45객체 지향 언어, 10 소멸자, 35 속성, 10다중 쓰레딩, 53 static, 13데이터 인스턴스, 10 쓰레드델리게이션, 20 다중 쓰레딩, 53런타임 안정성, 53 Objective-C, 65 역사리테인 순환, 49 Objective-C, 6매개변수 오버로딩 변수의 개수, 19 메서드, 15 익명, 19 연산자, 55, 56메모리, 40 함수, 15 alloc, 40 오토릴리즈 autorelease, 41 풀, 42 copy, 40 인라인, 55, 56 delete, 40 인자 mutableCopy, 40 디폴트 값, 19 new, 40 변수의 개수, 19 release, 40 익명, 19 retain, 40 인코딩, 54 참조 횟수, 40 자격심사 치환가능, 치환 불가능 복사, 38 in, out, inout, bycopy, byref, oneway, 26메소드, 8, 10 자동해제, 41 const, 19 전치 선언, 11 static, 19 접근자, 47 가상, 22 mutating, 45 순수 가상, 19, 23, 24 조사, 64 클래스 메소드, 13, 19 주석, 7 현재 메소드(_cmd), 18 참조, 55, 56멤버함수에 대한 포인터, 17 초기화, 29, 35문서 올바른 초기화, 30 개정, 71 초기화 리스트, 35문자열, 54 코코아, 6복사 74
  • 75. 클래스, 10, 44, 64 isKindOfClass, 64 isMemberOfClass, 64 NS 클래스, 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, 24 conformsToProtocol, 64 공식, 23 비공식, 25 자격심사 in, out, inout, bycopy, byref, oneway, 26프로토타입, 13, 15 modifiers, 19프로퍼티, 59 구현, 61 속성, 60 접근 문법, 62하향 형변환, 21함수, 8형 BOOL, 7 id, 7, 10 SEL, 7확장자 .mm, 66 75