이없을수있다면서꺼린다고것이다. 그래서필자는맥플랫폼의선진적인개발환경과오픈소스정책이생산성에큰도움을주는게아닌가하고조심스럽게추측해본다. 그중심에바로 코코아 (Cocoa) 가있다. 코코아로가장많은프로젝트를수행하는애플에서코코아의개발퍼포먼스에대해어떻다는공식적인언급은없었지만지켜보는사람들이그퍼포먼스가뛰어나다는것을미루어짐작할수있는부분이다. method definitions 예를들면애플에서제공하는레퍼런스에서 NSString 클래스를볼수있는데, 이것과별개로 NSString Additions라고된부분도볼수있다. NSString은기본클래스이고 NSString Additions는카테고리로더추가된경우이다. 이클래스에는다음과같은메쏘드도들어있다. 이번호에는오브젝티브-C의중요한두가지기능인 카테고리 와 프로토콜 에대해알아볼것이다. 지난호의내용이너무딱딱한것같고이번호또한무척건조한느낌이드는게사실이다. 하지만이부분을제대로이해해야코코아의장점을제대로살릴수있을것이라고생각한다. 설령지금이해가안되는부분이있다면앞으로진행되는연재를보면서가끔돌이켜보면좋을것이다. 추후에예제코드를보면서다시보면훨씬쉽게와닿을수있기때문이다. 난 6월 23 일부터 27일까지는전세계의맥개발자들플이쏟아내는제품들을보면과연 200명으로그걸다할수에게축제기간이었다. WWDC(World Wide 있는지의심이들지않을수없다. 참고로마이크로소프트 Developers Conference) 가열린것이다. 새로운 IDE와향 ( 이하 MS) 의맥관련소프트웨어를개발하는부서인 맥비상된컴파일, 디버깅기능, 새로운버전의 OS( 코드명지니스유닛 의총원은대략 150명가량된다고한다. 여기 Panther), 새로운하드웨어, 새로운주변기기가쏟아져나서만들어내는제품은매킨토시용워드, 엑셀, 파워포인트, 왔다. 사실이번 WWDC 기간동안세션에참가해서도많은앙트라쥬 ( 아웃룩익스프레스같은프로그램 ), MSN 메신저, 걸배웠지만애플에서근무하는몇몇개발자들과의만남이미디어플레이어, 인터넷익스플로러등이다. 대충어림짐작더욱인상에남았다. 으로도애플직원 1명당처리하는일이거의 MS 직원 10명애플의소프트웨어개발자는놀랍게도 200명정도에불과이상의업무량이라고봐도무방할것같다. 하다고한다. 지금까지애플개발자들이내놓은제품은 Mac 그런데필자가애플주위를어슬렁거렸을때애플직원들 OS X을비롯해개발도구, 퀵타임, i애플리케이션 (iphoto, 이매우열심히일하는것같이보이지는않았다 ( 우리나라 ichat, itunes, idvd 등 ), 키노트, 파이널컷시리즈, 웹오의근무태도를기준으로 ). 그렇다고애플직원들이 MS 직브젝트, 애플웍스, 사파리, 각종서버용관리툴등이다. 애원보다더뛰어나다고보기도힘들것같다 (MS는각대학에서뛰어난인재를많이끌어모으기로유명하다 ). 그렇다면과연이런생산성의차이는어디서오는것일까. 애플개발자들이혹자가말하는애플광신도 (?) 이기때문에 MS 의개발자보다정신무장이더잘되어있다고생각할수도있겠지만필자가듣기로는애플에서는자사의광신도들만뽑지는않는다고한다. 오히려공정하게바라보는시각 카테고리카테고리 (categories) 에대해설명하자면 1개의클래스안에서여러메쏘드들을적당한기준으로분류해서정리해줄수있게해주는기능 이라고간단하게정의할수있다. 물론이것만있는것은아니다. 오브젝티브-C의카테고리를잘이용하면 C++ 와같은언어로는상상할수도없는마술같은일이가능해지기도한다. 예를들어클래스를상속하지않고기존클래스를확장할수있는방법을제공하는것이좋은예가된다. 비록그클래스의소스가없다고하더라도말이다. 카테고리의사용목적은다음과같다. 클래스를연관된몇개의그룹으로나누어서관리할수있게해준다 ( 즉하나의클래스를몇개의조각내에서관리할수있게해준다. 구현파일의크기가커지고메쏘드들이많아지면상당히유용하다. 또한공동작업을하게될때각각의개발자들이자신의작업영역을같은클래스내에서도명확하게구분짓게할수도있다 ). 기존의클래스를상속받지않고새로운메쏘드를추가할수있게해준다. 인포멀프로토콜 (informal protocol) 을구현할때사용된다. 클래스에카테고리추가방법클래스에카테고리를추가하는방법은비교적간단하다. 인터페이스파일에카테고리로분류해서메쏘드를정의한다음카테고리이름과같은파일의구현파일을작성하면된다. 카테고리는새클래스를만드는것이아니고기존클래스에메쏘드가더추가되는것을말한다. 다음과같은방법으로사용하면된다. #import ClassName.h @interface ClassName (CategoryName) method declarations #import CategoryName.h @implementation ClassName (CatrgoryName) - (void) drawatpoint: (NSPoint )apoint withattributes: (NSDictionary *) attributes 이메쏘드는이름에서짐작할수있듯이화면에문자열을그려주는역할을한다. 이는필자가가장흥미롭게생각하는부분으로, 코코아프레임워크는그려야하는객체에그려주는메쏘드들이존재한다. MFC의 CString에는이런부분이존재하지않는다. 어느것이더객체지향적인지에대한논의를하기전에이메쏘드를소개하는이유는이메쏘드가카테고리로구현되었기때문이다. 즉 NSString에기본적으로는드로잉하는메쏘드가존재하지않지만카테고리를이용해서추가하게된것이다. 또원한다면카테고리로새로운메쏘드들을더추가할수있다. 물론 NSString의소스코드도필요없다. 또한카테고리로추가된메쏘드들은일반적인다른메쏘드처럼상속되기도한다 ( 이점은아주중요한문제로색다른문제를야기하기도한다 ). 카테고리사용예실제로카테고리를어떻게쓰는지알아보자. K&R의유명한 The C Programming Language 라는책에는섭씨, 화씨온도를변환하는예제가있다. 여기에서영감을받은 (?) 예제이다. 먼저 NSExample이라는클래스를만들어서이클래스가계산을하도록해보자. 프로젝트빌더또는 Xcode에서새프로젝트를선택한다. 그다음아래쪽에있는파운데이션툴을선택해서프로젝트를생성시킨다. 일반적으로는코코아애플리케이션같은걸사용하겠지만지금은간단한예제를만들것이기때문에 UI를사용하지않는파운데이션툴을선택한것이다. 메인프로그램은다음과같다. int main (int argc, const char * argv[]) NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; // insert code here... int ret; 300 301
NSExample *example = [[NSExample alloc] init]; 구현파일은다음과같이수정했다. 이간단한예제에서볼수있듯 지않겠다고약속했으니새파일을만들자. NSExampleAdditions.h [example setfahr:200]; [example calc]; ret = [example celsius]; NSLog(@ %d\n, ret); - (void)setfahr:(int)afahr fahr = afahr; 이카테고리사용법은다끝난것이나마찬가지다. 필요하다면카테고리이름으로새로운파일을만들어서구현할수도있다. #import NSExample.h 와 NSExampleAdditions.m 을만들었다. [example release]; - (int)celsius @interface NSExample (NSExampleAdditions) return 0; <main.m> 이처럼단순히화씨온도를입력한후에계산하라는메시지를주고섭씨온도를받아오는게전부이다. NSLog는 printf와비슷한데, NSString 값을로그창으로출력해주는역할을한다. 지난호에도설명했듯이 @ 을붙인문자열은 NSString을말한다. NSAutorelease Pool과객체의생성 소멸에대해서는뒷부분에도설명이나오므로일단넘어가도록하자. return celsius; - (void)setcelsius:(int)acelsius celsius = acelsius; - (void)calc celsius = 5 * (fahr - 32) / 9; <NSExample.m> @implementation NSExample - (void)calc celsius = 5 * (fahr - 32) / 9; @implementation NSExample (Accessors) - (int)fahr return fahr; - (void)loop; <NSExampleAdditions.h> #import NSExampleAdditions.h @implementation NSExample (NSExampleAdditions) - (void)loop int lower, upper, step; lower = 0; upper = 300; 여기서 calc 를제외한다른메쏘드들은 accessor 라고명명했다. 지 - (void)setfahr:(int)afahr step = 20; @interface NSExample : NSObject @private int fahr, celsius; - (int)fahr; - (void)setfahr:(int) afahr; - (int)celsius; 금은소스코드가짧아서그렇지만소스코드가무한정길다면결국이부분을따로관리하고싶다는생각을하게될것이다. 이제부터 accessor 메쏘드를따로카테고리로분리시키겠다. 헤더파일을다음과같이수정했다. fahr = afahr; - (int)celsius return celsius; fahr = lower; while ( fahr <= upper ) celsius = 5 * (fahr - 32) / 9; NSLog(@ %d\t%d\n, fahr, celsius); fahr = fahr + step; - (void)setcelsius:(int) acelsius; - (void)calc; <NSExample.h> 지난호에설명한방법대로일반적인클래스를선언했다. 구현역시단순하다. 여기까지는복습이라고생각하고다음단계로넘어가보자. @interface NSExample : NSObject @private int fahr, celsius; - (void)calc; - (void)setcelsius:(int)acelsius celsius = acelsius; <NSExample.m> <NSExampleAdditions.m> 그리고 loop라는메쏘드를만들어서구현했다. 여기서 fahr와 celsius 변수는 NSExample의것을그대로이용했다. main.m은다음과같이바꾸었다. @implementation NSExample - (int)fahr return fahr; @interface NSExample (Accessors) - (int)fahr; - (void)setfahr:(int) afahr; - (int)celsius; - (void)setcelsius:(int) acelsius; < NSExample.h> 그럼기존클래스에새로운기능을추가해보자. 현재 NSExample 클래스는단순하며실제로 K&R 책의예제와같지도않다. K&R 책의예제에서는루프를돌리면서온도를쭉출력하는기능이있다. 이기능을추가해보자 ( 단기존의 NSExample 클래스는손대지않고 ). 즉 NSExample 클래스를벤더에서제공한클래스라고생각하고, 이제개발자가기존에제공된클래스를상속받지않고기능을추가하는작업을한다고가정하자. 일단 NSExample은단한줄도건드리 #import NSExampleAdditions.h int main (int argc, const char * argv[]) NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; // insert code here... 302 303
NSExample *example = [[NSExample alloc] init]; 오브젝트가루트클래스의인스턴스메쏘드를실행할수있다는데 이때동시에여러개의프로토콜을지원하게하려면콤마 (,) 를넣 @interface MyObject : NSObject <MyProtocol> [example loop]; [example release]; 주의해야한다 ( 일반적으로인스턴스화되지않은클래스오브젝트에서바로인스턴스메쏘드를호출할수없다. 하지만특별히루트클래스의인스턴스메쏘드는상속받은다른클래스오브젝트에서도사용 어서추가할수있다. @interface Formatter : NSObject < Formatting, Prettifying > return 0; <main.m> 할수있다. NSObject의인스턴스메쏘드를밑에서오버라이딩하면이러한특징은사라진다 ). 프로토콜 프로토콜을따르는지확인소스코드에서 @protocol() directive를이용함으로써프로토콜오브젝트를사용할수있다. <MyObject.h> NSObject 를상속받은후아무내용을추가하지않았다 ( 실제상황 프로토콜 (protocols) 은클래스상속에서떨어져있는메쏘드정의리 에선이렇게하지않을것이다 ). 단지차이가있다면 MyProtocol 을 이제카테고리의가장중요한기능인파티션해주는기능과새로운 스트라고할수있다. 즉클래스상속의관계와무관하게, 특정클래 Protocol *myprotocol = @protocol(myprotocol); 준수할것이라고선언한것뿐이다. 메쏘드를추가해주는기능을확인해보았다. 여기서예제를만들지는 스정의와무관하게클래스들간의공통적인메쏘드를정리할수있는 않았지만만약기존의메쏘드와같은메쏘드를카테고리로추가하게 메쏘드리스트인것이다. 이는자바의인터페이스와유사하다. 만약어떤오브젝트가주어지면 conformstoprotocol: 메시지를 #import MyObject.h 되면기존의메쏘드를덮어버리게된다. 즉상속을해도기존의메쏘드를사용할방법이없다는것이다. 만약상속을받았다면 super를 프로토콜의용도 오브젝트에보내서프로토콜을따르는지확인할수있다. 결국 conformstoprotocol: 메시지를이용해그오브젝트가특정프로토 @implementation MyObject 이용해서기존의메쏘드에접근할방법은있지만카테고리는그렇지 주로두가지목적에서프로토콜을사용하는데, 하나는클래스를알 콜을따르는지확인한후사용할수있다. - (void)mymethod 못하다. 이것은잘생각해보면큰문제일수도있다. 카테고리는어떻게사용하느냐에따라서매우위험할수도있다. 자칫잘못하면전혀엉뚱한결과를낳을수있기때문이다. 또한공용으로사용하는특정클래스의메쏘드를카테고리로바꿔버리면다른작업자들이나소스의다른부분에서문제를일으킬수도있다. 즉메쏘드 수없는어떤객체를받아서그객체의사용여부를판단할때이다. 예를들어분산객체환경과같은경우다른호스트에서선언되고생성된어떤객체를네트워크를통해전달받아이객체를사용할때필요한프로토콜을만족하는지 conformstoprotocol: 메쏘드를이용해확인한후사용하게된다. 또다른주요용도로는어떤목적을위해 if ( [receiver conformstoprotocol:@protocol(myprotocol)] ) [receiver mymethod]; NSLog(@ My Method ); <MyObject.m> 를덮어버리는카테고리의남발은매우위험하며신중해야한다. 반드시구현해야하는메쏘드집합을선언할때이다. 예를들어코코 프로토콜의특징 반면카테고리가훌륭한부분은얼마든지있다. 예를들어특정인 아의 NSCoding 프로토콜은코코아객체의파일보관매커니즘 프로토콜은상속되거나중첩선언되지못하며선언된모든메쏘드는 구현에서도역시 MyProtocol 에정의된 mymethod 만을구현했다. 코딩을지원해야하는데 NSString 에서는해당인코딩을지원하지 (archiving mechanism) 으로, 이프로토콜의메쏘드를모두구현해 구현되어야한다. 메쏘드들을부분적으로사용해야할때는인포멀 여기서주의할점은프로토콜에정의된모든메쏘드를구현하지않으 않는다고가정해보자. 카테고리가없으면 NSString 을상속받아서 야비로소보관가능한객체가된다. 프로토콜을이용해야한다. 면실행시간에없는메쏘드를찾다가예외 (exception) 를발생시킨다 기능을추가하고그클래스를이용해야할것이다. 이작업은의외로 는것이다. 이경우메쏘드내의남은부분을처리하지않고빠져나와 상당히번거로운문제를야기할수있다. 특히이미완성된수만라인 프로토콜의사용 프로토콜사용예 서버그를발생시킬수있으나 -conformstoprotocol: 메시지에의해 의코드에서 NSString 을모두교체하고확인하려면적지않은부담 프로토콜의정의는다음과같이할수있다. 카테고리와마찬가지로파운데이션툴로간단히프로토콜의작동을보 프로토콜검사를할때실패하기때문에꼭확인을한다음사용하는 이될것이다. 하지만이경우에도카테고리는아주단순하고명확한 이는예제를만들어보자. 먼저 MyProtocol.h 를다음과같이만든다. 것이좋다. 특히컴파일할때경고 (warning) 를발생시키므로꼼꼼하 방법을제시해줄수있다. @protocol ProtocolName 게살펴봐야할것이다. 루트클래스카테고리카테고리는어떤클래스에도메쏘드를추가할수있다고했는데, 만 method declarations @protocol MyProtocol - (void)mymethod; #import MyObject.h 약루트클래스라면문제가좀특별해진다. 지난호에도설명했듯이 어떤클래스를선언할때프로토콜을추가하려면다음과같이한다. int main (int argc, const char * argv[]) 코코아에서모든클래스의루트클래스는 NSObject이므로여기에카테고리로추가된메쏘드는모든오브젝트에공통으로사용된다. 이 @interface ClassName : ItsSuperclass <protocol list> <MyProtocol.h> NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 는자칫하면매우위험한코드가될수있다는점을내포하고있다. 이 MyProtocol 을준수하는모든오브젝트는 mymethod 를구현해 id myobject = [[MyObject alloc] init]; 상속된모든클래스에서예상치못한동작을야기할수도있기때문 또한카테고리에프로토콜을추가할수도있는데, 다음과같이하 야한다. 다음으로 NSObject 를상속받은 MyObject 를만든다. id nsobject = [[NSObject alloc] init]; 이다. 그래도꼭해야하는경우엔루트클래스의 super는없으므로 면된다. if ( [myobject conformstoprotocol:@protocol(myprotocol)] ) [myobject mymethod]; super 로메시지를보내지못하며 ( 컴파일러가에러를낸다 ) 클래스 @interface ClassName (CatergoryName) <protocol list> #import MyProtocol.h 304 305
if ( [nsobject conformstoprotocol:@protocol(myprotocol)] ) 블데이터갱신이필요할경우에만구현하면되고, 나머지세메쏘드 예를들어문자열객체를인자로넘겨주어야할경우엔다음과같이 그럼이제처음에배웠던간단한객체생성코드에서이 retain [nsobject mymethod]; 들은드래그앤드롭의지원여부에따라구현해주면된다. 객체의생성과소멸 만들면된다. - initwithstring:(nsstring *)string; Count가어떻게동작하는지알아보자. 다음의코드를실행시켜보면간단한해답이나온다. [myobject release]; [nsobject release]; return 0; <main.m> 객체지향개발환경에서객체의생성과소멸은가장기본적이면서도가장중요한부분이지만 C++ 나자바와달리오브젝티브-C에서는별도의객체를생성하거나소멸하는데사용하는키워드가존재하지않는다. 또한오브젝티브-C는안타깝게도가비지컬렉팅 (garbage collecting) 기능을갖추고있지도않다. 그러나오브젝티브-C에서의 이객체의클래스가 MyObject라할때객체의생성은다음과같이하면된다. anobject = [[MyObject alloc] initwithstring:@ Sample ]; anobject1 = [NSObject new]; anobject2 = [[NSObject alloc] init]; NSLog(@ %d, [anobject1 retaincount]); NSLog(@ %d, [anobject2 retaincount]); 룰을잘이해한다면그리걱정할건없다. 오브젝티브 -C 에서는여러 앞에서도언급했지만사실두생성코드는같다. 따라서결과도같 마지막으로 main.m 의구현이다. 비교사례를들기위해 My 모로편리하게생성 소멸관리를도와주는장치들이준비되어있기 이코드는 -init 메쏘드를 -initwithstring: 으로대체시킨것이다. 다. 답은 1 이다. 다음을또보자. Protocol 을준수하는 MyObject 와 MyProtocol 을준수하지않는 때문이다. 물론한번에객체를생성시켜주는 +new 도얼마든지대체시킬수 NSObject 를같이생성한다음 -conformstoprotocol: 메시지를이 있다. +newwithstring: 을만들어 +alloc 과 -initwithstring: 을불 anobject = [[NSObject alloc] init]; 용해서 mymethod 를불러주게했다. 생성된객체가정확히무엇인 NSObject 의인스턴스를만들어보자 러주면된다. 이외에도객체를바로만들어주는클래스메쏘드들이 지모른다는것을보이기위해서 id 타입으로캐스팅했다. 실행결과를보면 myobject의 mymethod는부르지만 nsobject에서는 mymethod를부르지않는다. 다만 MyObject 외에또다른 MyProtocol을준수하는객체에대해서는메쏘드를호출할것이다. 인포멀프로토콜 NSObject의인스턴스를만드는방법은몇가지있는데, 이중에서가장쉬운것부터해보자. 다음의코드는 anobject 변수에 NSObject 의인스턴스를생성해서대입시킨것이다 ( 지난호에서도설명했듯이 NSObject는코코아프레임워크의루트클래스이고사실상아무것도아니지만루트클래스이기때문에다른모든서브클래스들에대해서도동일하게사용된다. 객체의생성과소멸을관리하는대부분 존재한다. 예를들어 NSString 클래스를살펴보면 +string이라는클래스메쏘드가있다. 이클래스는빈문자열객체를생성시켜준다. +new로도똑같은일을할수있지만 +string은분명히다른점이있다. 이제설명하는내용을보면뭐가다른지알수있을것이다. retaincount를이해하자 [anobject retain]; [anobect release]; [anobject release]; 프로토콜을이용하지않은채카테고리를선언하고이를구현하지않 역할도 NSObject 에구현되어있다 ). 혹시메쏘드를생성했는데어떻게소멸시키는지언급하지않는데대 는방법으로프로토콜과비슷한기능을구현할수도있다. 이것을인 해궁금해하는독자가있을까. NextStep 에서는 -free 라는메쏘드가 이번에는객체를생성시킨다음 -retain 과 -release 를불러보았 포멀프로토콜 (informal protocols) 이라고하는데모든서브클래싱 anobject = [NSObject new]; 있었다. 이메쏘드가객체를소멸시켜주는역할을했지만 OpenStep 다. retaincount 는어떻게변할까? 답은 1, 2, 1 이다. 하지만이코드 된클래스들에서구현해도되고하지않아도될메쏘드그룹을정의 부터는없어졌다. 그럼어떻게할까? 정답은 소멸시키지말라 이다. 에서는중요한것이하나있다. 마지막에부른 -release 는바로전에 하는데이용한다. 인포멀프로토콜은흔히 NSObject 의카테고리로 이코드는매우간단하지만많은것을내포한다. 객체가메모리에할 정확히말해서소멸시킬필요가없다. 그렇다면자동으로소멸시켜준 부른 -release 와달리객체를완전히소멸시켜버린다는것이다. 왜 선언되는데, NSObject 에서상속된모든클래스에광범위하게영향 당되며인스턴스변수나객체에대한초기화를하게된다. 이코드는 다는것일까? 앞서말했듯이그건아니다. 냐하면 retaincount 가 0 이되는순간이니까. 나중에 anobject 를사 을미치게된다. 이런이유로일반적인프로토콜을사용할때의사용 NextStep 애플리케이션소스에서는많이찾아볼수있지만필자는 코코아프레임워크에서는모든객체가 retaincount 를가지고있 용하면곧바로런타임에러가발생하게된다. 이미소멸되어없어진 은피하는것이좋다. 모든메쏘드의구현을선택적으로할수있으므 OpenStep 이후잘보지못했다. 하지만지금도잘동작하며, 단지좀 다. retaincount 란쉽게말해객체의 reference count 라고할수있 객체를사용하려고했으니까. 로딜리게이트 (delegate) 처리같은부분에서사용된다. 예들들어 명확하지못하다는것일뿐이다. 이코드는다음코드와똑같다. 다. 즉 얼마나많이참조되고있느냐 라는것이다. 가비지컬렉션이 한가지더중요한것은처음에객체를생성하고난뒤 retain 코코아의 NSTableDataSource 프로토콜은인포멀프로토콜이다. 지원되는언어들과차이점은이 count 를프로그래머가직접제어해 Count 가 1 이라는사실이다. 당연한게아니냐고반문할수도있겠지 NSTableDataSource 프로토콜의인포멀프로토콜을루트클래스 anobject = [[NSObject alloc] init]; 야한다는것이다. NSObject 에구현된 -retaincount 는 unsigned 만, 필자가말하고싶은것은 +alloc 메쏘드에있다. +alloc 은객체 인 NSObject 클래스에카테고리로붙여놓은이유는어떤클래스에 int 형의숫자를돌려주는데, 이숫자가얼마나레퍼런스되고있는지 가필요한메모리를할당하는것외에도 retaincount 를 0 에서 1 로 서도이카테고리의메쏘드를구현할수있도록하기위해서이다. 만 +new 메쏘드는클래스메쏘드로, 클래스에게 +alloc 과 -init 을 를알려준다. 프로그래머는객체에 -retain 과 -release 를불러서 증가시켜준다는것을잊지말자. 약 MyDataSource 라는클래스가이프로토콜을사용하고싶으면 연속적으로부른다. +alloc 메쏘드는클래스메쏘드로, 클래스가인 retaincount 를증가시키고감소시킬수있다. MyDataSource 클래스에다앞서선언된메쏘드들을구현해주기만 스턴스화할때객체가필요로하는메모리를할당하는역할을한다. 객체는 retaincount 가 0 이되는순간소멸된다. 좀더자세히설명 생성된객체사용하기 하면된다. -init 은인스턴스메쏘드로인스턴스화된객체를최종적으로초기화 하면 -retain 은객체의 retaincount 를 1 증가시키고 -release 는 1 retaincount 를이해했다면이제본격적으로응용해보자. C 프로그 NSTableDataSource 인포멀프로토콜의경우선언된메쏘드들 하게된다. 어떻게보면 C++ 나자바의 Constructor 는오브젝티브 - 감소시킨다. retaincount 는객체가 0 인지확인하고 0 이라면객체를 래밍을공부할때선생님이나선배들로부터귀에못이박히도록들은 중처음의두메쏘드 (numberofrowsintableview: 와 tableview C 의이 -init 메쏘드와같은역할을한다고할수있다. 완전히소멸시키는것이다. 이 retaincount 만정확히이해하고있으 잔소리가있을것이다. 메모리가필요하면반드시 malloc 으로할당 objectvaluefortablecolumn:) 는반드시구현해야하지만나머지 가끔객체를초기화시킬때필요로하는인자를요구할수있는데, 면되는데, 프로그래밍할때조금만주의를기울여룰을지켜준다면 받고사용이끝나면반드시 free 해주어라 라는말이다. 즉돼지같은 메쏘드들을필요에따라구현해주면된다. 즉세번째메쏘드는테이 이경우는 init 로시작하는메쏘드들을만들어나감으로써가능하다. 가비지컬렉팅지원언어가부럽지않을만큼쉽다. 프로그램이되지않으려면자신이할당받은자원은반드시해제해주 306 307
어야한다. 그렇지않으면프로그램이종료될때까지시스템의자원 는사용이끝났는데 -release 가불려지지않는다면소멸되지않을것이 id anobject = [[NSObject alloc] init]; 다. NSAutoreleasePool 객체가애플리케이션이시작하면서부터종 들은폐허가되어버린다. 객체도똑같다. 객체를필요로해서받아왔으면본격적인사용에들어가기전에 -retain을불러줘객체가사용되고있음을알려야하 다. 다시말해객체가소멸될때그객체의인스턴스변수에존재하는객체들마저자동으로처리될것이라고는절대로기대하지말라. 이시점에서 -dealloc 메쏘드를소개한다. 이메쏘드는자바의... return [anobject autorelease]; 료될때까지단하나만존재한다면어차피애플리케이션이종료되면모든자원이 OS에의해회수될것이기때문에굳이 NSAutorelease Pool을사용할필요가없을것이다. 여러개의 NSAutoreleasePool 고, 사용이끝나면 -release 를불러더이상필요없는객체는소멸되 finalize() 와비슷하다. 객체의 retaincount 가 0 이되어소멸하게되 객체는스택 (stack) 으로존재해서 autorelease 되는객체들은가장마 도록해줘야한다. C 프로그래밍을가르쳐준선배들처럼필자도잔 면오브젝티브 -C 런타임은객체에 -dealloc 메쏘드를불러소멸시키 이번에는 release 시키지않고 -autorelease 의리턴값을돌려주었 지막에만들어진 NSAutoreleasePool 객체에보존된다. 소리를한마디하려고한다. retain 한객체는반드시 release 해주 게된다. 하지만프로그래머가결코 -dealloc 을부르지는않는다. 소 다. -autorelease 는리시버를리턴하며 autorelease 가된다. 그럼 일반적인 AppKit 애플리케이션의경우코코아프레임워크에의해 어라 retain 만해주고 release 를하지않으면그객체는프로그램이 멸이필요하면자동으로불려진다. 개발자는 -dealloc 메쏘드만적절 autorelease 는자동으로 release 해주는것인가. 아니면조금있다가 이벤트가발생할때마다 NSAutoreleasePool 을생성했다가소멸시 종료될때까지소멸되지않는다. 만약그프로그램이서버프로그램 하게구현해놓으면끝이다. release 시켜주는것인가. autorelease 를이해하기위해서는코코아 킨다. 따라서이벤트처리시만들어지는객체들은자동소멸하게되는 이라면? 아주치명적인결과를얻을것이다. 필요해서 retain 을해주 -dealloc 메쏘드를구현할때반드시지켜야할것은단한가지인 프레임워크에있는 NSAutoreleasePool 이라는클래스를알필요가 것이다. 만약 AppKit 을사용하지않는서버프로그램을작성한다면 고필요없어서 release 를해주었음에도불구하고프로그램이런타임 데, 마지막에 super 의 -dealloc 을불러주기만하면된다. 바로앞의 있다. NSAutoreleasePool 은객체를가지고있다가풀이소멸될때 프로그래머는 NSAutoreleasePool 을적당히사용해애플리케이션이 에러를내며종료된다면, 그것은프로그래머가잘못한것이지이룰 예제에서 MyObject 의경우 -dealloc 메쏘드의구현은다음과같다. 가지고있던객체들에게 -release 메시지를보내는역할을한다. 즉 쓸데없는시스템자원을차지하게되는현상을막아야한다. 의잘못은아니라는점을확실하게말하고싶다. autorelease 를사용하면당장 release 되지않고 NSAutoreleasePool 그리고노파심에서덧붙이자면앞에서도언급했듯이 +alloc도 retaincount를증가시키기때문에 +alloc도곧 retain을한것이나마찬가지로볼수있다. 그러므로다음과같이말할수있다. +alloc 으로생성시켰으면역시반드시 release해주어라 - (void)dealloc [object release]; [super dealloc]; 이소멸될때 release되는것이다. 이렇게함으로써객체가얼마간소멸되지않도록해준다. 다음의코드를보자. pool = [[NSAutoreleasePool alloc] init]; 잠시사용할객체얻기앞서 NSString의객체를생성하는메쏘드들중에 +new를이용하거나 +alloc과 -init을연속으로불러서객체를얻는방법외에국적없는 (?) +string이라는메쏘드를언급한적이있다. 이메쏘드는새 간단한예를하나들어보겠다. 만약 MyObject 클래스를구현하는데, 이객체가 -setobject: 라는메쏘드를통해다른객체를받아서사 MyObject가사용하기위해 retain을걸어놓았던 object를마지막으로 release해주고나머지는 superclass에맡긴다. 만약 super에 - anobject = [[NSObject alloc] init]; 로운빈문자열객체를생성해서리턴하기때문에역시생성을위한메쏘드임에는틀림없다. 하지만이메쏘드가다른점은객체가리턴 용하고있다고하자. -setobject: 를간단히구현한코드를보자. dealloc을부르지않으면객체가소멸된것처럼보이지만완전히소멸되지않았다는것은새삼설명할필요가없을것이다. [anobject retain]; 될때 -autorelease가한번불려진다는점이다. 즉잠시동안사용할문자열객체에대해서 +alloc과 -init을이용해생성한다음사용을 - (void)setobject:(id)anobject [object release]; object = [anobject retain]; anobject 는 MyObject 에넘겨주는객체이고 object 는 MyObject autorelease 만약독자여러분이객체를하나생성하고리턴해주는메쏘드를만는상황을가정해보자. 앞에서무척강조했던 retain한객체는반드시 release해주라 는룰을지킬수있을까? 다음의코드를보자. [anobject release]; [anobject autorelease]; 마치고 -release를부르는것이귀찮을정도라면 +string을사용하라는것이다. 그럼객체는이미 NSAutoreleasePool에추가되어서풀이소멸될때 release되는운명을가지고생성되는것이다. 객체복사하기 가 anobject 를받아서사용하게될인스턴스변수이다. 이코드는 이외에도새로운객체를생성시키는방법으로이미생성되어있는객 anobject를새로받을때기존의 object에 -release를불러서사용이끝났음을알리고, 새로받게되는 anobject에 -retain을불러이제사용할것이라고알린다 ( 참고로 -retain 메쏘드는리시버 (receiver) - (id)objectforme id anobject = [[NSObject alloc] init];... [anobject release]; 결과는 1, 2, 1, 1이나오고, 결국 -autorelease가호출되더라도 retaincount가감소되지않는다는것을알수있다. 하지만풀이 체의복사본을얻는방법이있다. 복사라는말은객체를하나더만들되그객체의내용을원본객체의내용과똑같게하는것이다. 객체를복사하는메쏘드는 -copy이다. 다음코드를보자. 를리턴한다 ). 이렇게하면매번다른객체로 -setobject: 를하더라도정확히 MyObject에의해 retain/release되어객체가사용중소 return anobject; release되어소멸되는순간 anobject도 release되기때문에이때 anobject는소멸하게된다. NSAutoreleasePool 객체는코코아애 anotherobject = [anobject copy]; 멸되지않을뿐만아니라아무도사용하지않게되는객체는소멸됨 플리케이션에서는항상존재하며, 만약하나도없으면객체가생성될 으로써깨끗한프로그램이될수있다. 단도직입적으로이소스코드는완전히틀렸다. 코드가잘못됐다는 때런타임경고가발생한다. 이는소멸되어야할객체가소멸되지않 anobject 는이미존재하는객체라고가정하고이코드는 것을이해한다면왜 autorelease 가필요한지알것이다. 이코드에서 았다는것을의미한다. 코코아프레임워크에존재하는대부분의객체 anobject 와내용이똑같은새로운객체를생성해서 anotherobject 객체가소멸될때 리턴해주는 anobject 는 -release 메쏘드가호출되면서소멸해버려 들은이 NSAutoreleasePool 을사용하므로코코아프레임워크의객 변수에대입한다. 객체복사가끝난직후에는 anobject 와 another 앞에서생성한객체를소멸시켜보았다. 이제필요없는시스템자원을 전혀쓸모없을뿐만아니라런타임에러도발생할것이다. 이코드를 체를하나도사용하지않는오브젝티브 -C 프로그램을만든다면굳이 Object 가내용이똑같겠지만실제로는다른객체이기때문에복사후 돌려주게되어기쁜가? 하지만한가지남은문제가있다. 바로앞의예 맞게고치면다음과같다. NSAutoreleasePool 을사용하지않을수있다 ( 그러나 Mac OS X 을 anobject 에변경을가하더라도 anotherobject 에영향을미치지는 제의경우에서보는것처럼 MyObject 가소멸될때가지고있던인스턴 이용한다면아마도절대이런일은없을것이다 ). 않는다. 따라서없던객체를생성한꼴이된다. 복사된객체는원본 스변수에남아있는 object 는어떻게할것인가하는점이다. 이 object - (id)objectforme NSAutoreleasePool 은애플리케이션에서하나이상존재하게된 객체와같은클래스이고, 원본객체가가지고있던모든인스턴스변 308 309
수에대입되어있던객체나값들도새로운객체에복사된다. 객체를복사하는것에서한가지알아야할점은 -copy라는객체복사메쏘드에의해리턴되는새로운객체는 retaincount가 1이며, 바로앞에서봤던 +string과같은메쏘드처럼 autorelease되지않는다는것이다. 즉 +new를이용해서생성한것과같다. 그러므로복사 - (id)initwithx:(nsnumber *)x y:(nsnumber *)y; - (NSNumber *)x; - (void)setx:(nsnumber *)x; - (NSNumber *)y; - (void)sety:(nsnumber *)y; _x = [x retain]; _y = [y retain]; return self; - (void)dealloc 마지막으로 -initwithx:y: 메쏘드를잘살펴보기바란다. 알아두어야할사실은 -init... 메쏘드가생성된객체를초기화할때만약어떤이유에의해서초기화를할수없다면 nil을리턴한다는사실이다. 만약 super의 -init... 이실패해서 nil이리턴된다면초기화과정을거치지않고바로리턴한다. 혹시이경우초기화를하기위해객체를 해서사용한객체도사용이끝나면반드시 release해주어야한다는점을잊지말자. @implementation MyPoint [_x release]; [_y release]; [super dealloc]; 생성하게되면이객체들은영영소멸되지않게된다. 또한초기화과정에서도실패를하게되면 nil을리턴하는데, 이때는반드시 release해야한다는것을잊지말자. release하지않는다면역시이 객체생성을위한메쏘드아주특별한경우가아니라면코코아프레임워크의모든객체는객체 - (id)copywithzone:(nszone *)zone return [[[self class] allocwithzone:zone] initwithx:[[_x copy] autorelease] - (NSNumber *)x return _x; 객체는영영소멸하지않게된다. 좀더완벽한 Point 객체를구현하기위해서는여러가지예외처리 생성을위한메쏘드에네이밍룰 (naming rule) 이있다. 이룰을알게됨으로써굳이메쏘드의레퍼런스를보지않아도사용자가 release해주어야할지말아야할지를알수있다. 특히객체를디자인하고구현하는프로그래머의경우이룰을따라줌으로써다른사용자가좀더편리하게접근할수있도록할수있다. 현재까지알아본모든객체생성방법과함께간단하게다시정리해보면다음과같다. +alloc은객체를생성하고 retaincount를 1로만든다. -init... 은생성된객체를초기화하는메쏘드이다. +new... 는 +alloc과 -init... 이다. 클래스의이름을따온생성메쏘드 ( 앞의예 : +string) 는 +alloc, -init..., - autorelease이다 ( 가끔씩아닌경우도있지만중요한점은 release해줄필요가없 y:[[_y copy] autorelease]]; + (id)point return [[[self alloc] init] autorelease]; + (id)pointwithpoint:(mypoint *)point return [[[self alloc] initwithpoint:point] autorelease]; + (id)pointwithx:(nsnumber *)x y:(nsnumber *)y return [[[self alloc] initwithx:x y:y] autorelease]; - (id)init self = [super init]; if (self) - (void)setx:(nsnumber *)x if (x) [_x release]; _x = [x retain]; - (NSNumber *)y return _y; - (void)sety:(nsnumber *)y if (y) [_y release]; _y = [y retain]; 가필요할것이고더복잡해지겠지만, 여기서는생성과소멸문제를집중적으로다뤘기때문에이정도만해도충분하리라생각한다. 부족한글이지만약간이나마도움이되어더나은프로그램을만들수있게되기를바란다. 흥미로운코코아의세계를꿈꾸며이번호에는오브젝티브-C의특별한기능인카테고리와프로토콜에대해알아보았고, 덤으로객체의생성과소멸과정에대해서알아보았다. 사실여기에쓴내용보다더많고복잡한내용들이있으나카테고리와프로토콜에대해서이해하고나면사실상오브젝티브-C의기능에대해서는다배운거나다름없다. 이처럼오브젝티브-C 언어는단순한언어이며, C++ 과같이두툼한문법책이필요없다. 런타임시스템에대해서는좀더설명할내용들이있지만나머지부분들에대 다는것이다 ). -copy... 는 +alloc과 -init... 으로생성된같은내용의객체다. 그러므로이도사용후반드시 release해주어야한다. _x = [[NSNumber alloc] initwithfloat:0]; _y = [[NSNumber alloc] initwithfloat:0]; return self; 해서는연재를진행하면서필요할때마다조금씩다룰예정이다. 이번연재를진행하면서필자에게걱정이좀생겼다. 흥미롭게배울수있기로유명한코코아를너무재미없게시작하는게아닌가하는생각이들기때문이다. 처음부터질려서거들떠보지도않는독자들이생 Point 구현하기 - (id)initwithpoint:(mypoint *)point 참고로 Point 는 NSCopying 프로토콜을구현하고있다. - 긴다면전적으로필자의책임이다. 단언하건데가장어려운부분은이 지금까지설명한내용을중심으로 Point 클래스를구현해보도록하겠다. @interface MyPoint : NSObject <NSCopying> NSNumber *_x; self = [super init]; if (self) _x = [[point x] retain]; _y = [[point y] retain]; return self; copywithzone: 은 NSCopying 프로토콜에의한것이다. -copy는 NSCopying 프로토콜의 -copywithzone: 메쏘드를사용하므로 Point에서굳이 -copy를구현할필요는없다. 특히주의해서봐야할것은다음의차이이다. 번연재로끝이났다고봐도된다. 앞으로이어지는연재에서는신나고재미있고 (?), 가끔은경이로운개발환경을들여다보게될것이다. a m o s NSNumber *_y; + (id)point; + (id)pointwithpoint:(mypoint *)point; + (id)pointwithx:(nsnumber *)x y:(nsnumber *)y; - (id)init; - (id)initwithpoint:(mypoint *)point; - (id)initwithx:(nsnumber *)x y:(nsnumber *)y self = [super init]; if (self) if (!x!y) [self release]; return nil; point1 = [[Point alloc] initwithpoint:apoint]; point2 = [apoint copy]; point1과 point2는내용이같지만사실은좀다르다. - initwithpoint: 와 -copywithzone: 의구현상차이를잘살펴보라. 혹시좀더자세한내용을알고싶으면 Foundation 프레임워크의 NSCopying 프로토콜레퍼런스를읽어보기바란다. http://www.osxdev.org http://developer.apple.com http://www.gnustep.org Object-Oriented Programming And The Objective-C Language, Apple Computer, Inc. Cocoa Programming for Mac OS X, Aaron Hillegass Objective-C Poket Reference, O reilly, Andrew M. Duncan Learning Cocoa With Objective-C, O reilly, James Duncan Davidson Building Cocoa Applications, O reilly, Simson Garfinkel, Michael K. Mahoney Cocoa In a Nutshell, O reilly, Michael Beam, James Duncan Davidson 310 311