삽질하는플머

'Objective C'에 해당되는 글 3건

  1. MinGW-MSYS 에 GNUStep base 얹기 3
  2. Objective-C 초보자 가이드. 3
  3. Dev-C++ 에서 오브젝티브 C 가지고 놀기.

MinGW-MSYS 에 GNUStep base 얹기

탐구생활/Objective-C
왜 이 짓을 하냐고??
MinGW를 깔 때 Objective-C 컴파일러도 함께 설치해서 libobjc 가 깔려있는 상태지만, 
심심풀이삼아 Objective-C 코딩을 해보려면 NSObject 없이는 솔직히 아무것도 안되거덩.
하지만 그거 하자고 GNUStep 을 또 설치하기는 그렇잖아. 태국 홍수로 하드값이 비싸져 용량 늘리기도 힘든데...
게다가 GNUStep의 우중충한 GUI는 별로 관심없으니 base만 깔아보자는 이야기.

C:\MinGW 에 MinGW가 설치된 상태에서 C:\MinGW\GNUStep 에 필요한 환경을 구성하는 것이 목표임. 
다운로드 페이지는 여기. 
http://wwwmain.gnustep.org/resources/downloads.php?site=ftp%3A%2F%2Fftp.gnustep.org%2Fpub%2Fgnustep%2F 
이 중에서 GNUstep Base 가 오늘의 장난감이다. 

맨 밑에 Pre-requisites 항목에는 libffi, libxml, libtiff 가 필요하다고 나와있다. 
libtiff야 GUI에 쓰일테니 무시. libxml은 gnustep-base 구성시 --disable-xml 옵션으로 빼버릴 수 있으니 libffi만 설치하면 된다. 

$ mkdir build-gnustep
$  cd build-gnustep

$ wget ftp://sourceware.org/pub/libffi/libffi-3.0.9.tar.gz
$ tar -xzf libffi-3.0.9.tar.gz
$  cd libffi-3.0.9
$ ./configure --prefix=/mingw/GNUStep
$ make
$  make install



C:\MinGW\GNUStep 가 새로 생성되고 여기에 지금 생성된 녀석들이 들어감. 

/etc/profile 을 편집, /mingw/GNUStep/bin 을 PATH에 추가한다.

export PATH="$PATH:/mingw/GNUStep/bin"



셸 재구동 후 계속 진행하자. 재구동이 귀찮으면 wonderman님이 가르쳐주신 다음 방법을 써도 된다. 

$ source /etc/profile




동일한 방식으로 gnustep-make 다운로드 후 빌드. 

$ wget ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-make-2.6.2.tar.gz
$ tar -xzf  gnustep-make-2.6.2.tar.gz
$  cd gnustep-make-2.6.2 
$ ./configure --prefix=/mingw/GNUStep
$ make 
$ make install



 
이제 gnustep-base 다운로드 후 빌드. 구성옵션에 libffi 경로를 지정해준다.
libxml을 설치하지 않았으니 --disable-xml도 잊지 말 것. 

$ wget ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-base-1.24.0.tar.gz 
$  tar -xzf gnustep-base-1.24.0.tar.gz
$ cd gnustep-base-1.24.0
$ ./configure --prefix=/mingw/GNUStep \
--with-ffi-include=/mingw/GNUStep/lib/libffi-3.0.9/include \
--with-ffi-library=/mingw/GNUStep/lib \
--disable-xml
$ make
$  make install


 
끗! 

이제 테스트~ 테스트~~
 Dev C++을 열고 Objective-C용 컴파일 설정을 추가하자.


컴파일러 추가명령 : -fconstant-string-class=NSConstantString
링커 추가명령 : -lobjc -lgnustep-base
실행파일 디렉토리에 : C:\MinGW\GNUStep\bin 추가. 
라이브러리는 : C:\MinGW\GNUStep\lib
헤더는 : C:\MinGW\GNUStep\include



HelloWorld 예제를 만들어 돌려보면



뭐 잘 동작하는군. 




두 줄 요약.
- 굳이 GNUStep 을 따로 설치하지 않아도 단순히 Objective-C 학습용이라면 기존 MinGW 환경에 추가할 수 있다. 
- 하드 용량이 남아돈다면 그냥 GNUStep을 깔자.
 

덤. Dev-CPP에서 Objective-C 공부하기는 예전에 끄적여둔 http://oranke.tistory.com/141 를 참고.
 

Objective-C 초보자 가이드.

탐구생활/Objective-C
원문 : http://www.otierney.net/objective-c.html
이런... 한참 번역하다보니 이미 번역판이 있었네...  http://www.otierney.net/objective-c.html.ko 

위 가이드를 바탕으로 Dev-C++ 에서 학습한 내용을 정리해가자. 


시작하기

환경설정

윈도에서 Dev-C++과 Objective-C 학습 환경설정은 이전 포스트를 참고할 것. 


HelloWorld 만들기. 




* Dev-C++ 파일->새로만들기->프로젝트에서 "Empty Project" 선택. HelloWorld 프로젝트를 생성한다. 
* 프로젝트에서 우클릭 후 hello.m 유니트 추가하고 다음코드 입력.  

#import <stdio.h>

int main ( int argc, const char *argv[] ) {
  printf("Hello World\n");
  return 0;
}


* 실행->컴파일 하면  콘솔에 Hello World 를 출력하고 종료됨. 

Hello World


* #include 대신에 #import 를 사용하고 있다. 
Objective-c 의 확장자는 "*.m" 이다. 



클래스 만들어보기



@interface : 선언부 작성

* ClassTest 프로젝트를 만들고. Fraction.h 를 다음과 같이 작성. 

#import <Foundation/NSObject.h>

@interface Fraction: NSObject{
  int numerator;
  int denominator;                      
}

-(void) print;
-(void) setNumerator: (int) n;
-(void) setDenominator: (int) d;
-(int) numerator;
-(int) denominator;
@end;


* NSObject : NeXTStep Object 의 줄임말이다. 
* 상속관계는 위의 "Fraction: NSObject" 처럼 "Class: Parent" 형식으로 지정된다. 
* 인스턴스의 변수들은 @interface Class: Parent 다음의 대괄호 {...} 사이에 위치한다. 
* protected, public, private 같은 접근제어문이 없을 때 기본값은 protected 이다. 
* 인스턴스의 메소드들은 멤버변수들 뒤에 위치한다. 형식은 다음과 같다. 
  스코프 (리턴타입) 메소드이름: (파라미터1번_타입) 파라미터1번_이름;
  이 때 스코프는 메소드가 클래스메소드인지 인스턴스 메소드인지를 나타낸다. 클래스메소드일 경우 - 대신 + 를 사용한다. 
* interface 구문은 @end 로 종료한다. 


@implementation : 구현부 작성

* Fraction.m 유니트를 추가하고 다음과 같이 작성. 

#import "Fraction.h"
#import <stdio.h>

@implementation Fraction
-(void) print {
  printf( "%i/%i", numerator, denominator );
}

-(void) setNumerator: (int) n {
  numerator = n;
}

-(void) setDenominator: (int) d {
  denominator = d;
}

-(int) denominator {
  return denominator;
}

-(int) numerator {
  return numerator;
}
@end


* 구현부는 "@implementation 클래스이름" 으로 시작하고 "@end" 로 마무리된다. 
* 모든 메소드들은 interface 에 정의된 것과 매우 유사한 방식으로 구현되고 있다. 


재료준비 완료. 버무려보자.

* main.m 유니트 추가하고 다음과 같이 입력. 
 
#import <stdio.h>
#import "Fraction.h"

int main (int argc, const char *argv[]) {
  // 새로운 인스턴스 생성
  Fraction *frac = [[Fraction alloc] init];
  
  // 값 설정
  [frac setNumerator: 1];
  [frac setDenominator: 3];
  
  // 화면에 출력. 
  printf("The fraction is: ");
  [frac print];
  printf("\n");
  
  // 메모리 해제. 
  [frac release];
  
  return 0;
}


* 컴파일 후 출력은 다음과 같다. 

The fraction is: 1/3


* Fraction *frac = [[Fraction alloc] init];
  이 한줄에는 많은 중요한 의미들이 내포되어있다. 
  Objecive-C 에서 메소드의 호출은 [object method] 와 같이 이루어진다. 이는 C++의 object->method() 와 같다. 
  Objective-C 에서는 값의 타입이 없다. 다시 말해 C++처럼 Fraction frac; frac.print(); 이렇게는 안된다는 뜻이다. 항상 객체는 포인터로 간주해야 한다. 
  여기서 하는 일은 사실 두가지이다. 먼저 [Fraction alloc] 은 Fraction 클래스의 alloc 메소드를 호출한다. 이 단계에서 메모리 할당이 일어난다. 
  [object init] 는 생성자의 호출이다. 여기서 객체 내부의 값들이 초기화된다. 
* [frac setNumerator: 1] 은 frack 의 setNumerator 메소드를 호출하고 파라미터를 넘긴다. 
* release 는 메모리 해제이다. 이 부분에 대해서는 이후 좀 더 자세하게 다룰 것이다. 



클래스 깊이 파보기

여러개의 파라미터

* 여러개의 파라미터를 전달하는 방법을 살펴보자. 스몰토크에서 기반한 이 문법은 사실 처음에는 그다지 직관적이지 못하다. 
* Fraction.h 에 다음 메소드를 추가하자. (기존의 것 수정이 아니라 새로 추가하는 것임) 

......
-(void) setNumerator: (int) n andDenominator: (int) d;
......


* Fraction.m 에서 구현부를 만들어준다.

......
-(void) setNumerator: (int) n andDenominator: (int) d {
  numerator = n;
  denominator = d;
}
......


* main.m 수정. 

#import <stdio.h>
#import "Fraction.h"

int main (int argc, const char *argv[]) {
  // 인스턴스 생성
  Fraction *frac = [[Fraction alloc] init];
  Fraction *frac2 = [[Fraction alloc] init];
  
  // 값 설정
  [frac setNumerator: 1];
  [frac setDenominator: 3];
  
  // 두 값 한꺼번에 설정. 
  [frac2 setNumerator: 1 andDenominator: 5];
  
  // 화면에 출력. 
  printf("The fraction is: ");
  [frac print];
  printf("\n");
  
  // 두번째 객체 출력. 
  printf("Fraction 2 is: ");
  [frac2 print];
  printf("\n");
  
  // 메모리 해제. 
  [frac release];
  [frac2 release];
  
  return 0;
}


* 출력은 다음과 같다. 

The fraction is: 1/3
Fraction 2 is: 1/5


* 메소드 호출은 setNumerator:andDenominator: 와 같은 방식으로 이루어진다. 
* 더 많은 추가 파라미터는 두번째 파라미터와 동일한 방식을 사용한다. method:label1:label2:label3: 호출할 때에는 [obj method: param1 label1: param2 label2: param3 label3: param4] 식으로 한다. 
* Lable, 즉 이름표는 옵션이므로 생략할 수 있다. 따라서 메소드 이름을 method::: 라고 할 수도 있다. 하지만 추천하지는 않는다. 


생성자

* Fraction.h 에 다음과 같은 선언부 추가. 

......
-(Fraction*) initWithNumerator: (int) n denominator: (int) d;
......


* Fraction.m 에 구현부 추가. 

......
-(Fraction*) initWithNumerator: (int) n denominator: (int) d {
  self = [super init];
  if (self) {
    [self setNumerator: n andDenominator: d];
  }             
  
  return self;
}
......


* main.m 에서 사용은 다음과 같이.  

#import <stdio.h>
#import "Fraction.h"

int main (int argc, const char *argv[]) {
  // 인스턴스 생성
  Fraction *frac = [[Fraction alloc] init];
  Fraction *frac2 = [[Fraction alloc] init];
  Fraction *frac3 = [[Fraction alloc] initWithNumerator: 3 denominator: 10];
  
  // 값 설정
  [frac setNumerator: 1];
  [frac setDenominator: 3];
  
  // 두 값 한꺼번에 설정. 
  [frac2 setNumerator: 1 andDenominator: 5];
  
  // 화면에 출력. 
  printf("The fraction is: ");
  [frac print];
  printf("\n");
  
  // 두번째 객체 출력 
  printf("Fraction 2 is: ");
  [frac2 print];
  printf("\n");
  
  // 생성자로 만들어진 세번째 객체 출력
  printf("Fraction 3 is: ");
  [frac3 print];
  printf("\n");
  
  // 메모리 해제. 
  [frac release];
  [frac2 release];
  [frac3 release];
  
  return 0;
}


* 출력결과는 다음과 같다. 

The fraction is: 1/3
Fraction 2 is: 1/5
Fraction 3 is: 3/10


* 생성자는 @interface 부에서는 다른 함수들과 별다를 것 없이 생겼다. 

* 구현부 @implementation 에서 새로운 키워드 super 가 등장한다. 
  자바와 마찬가지로, Objective-C 는 하나의 부모클래스만 가진다. 
  "[super init]" 구문을 통해 부모객체의 생성자를 호출할 수 있다.  
  이 리턴값은 새로 등장한 또다른 키워드인 self 에 할당되고 있다. Self 는 자바, 또는 C++과 유사한 개념이다. 
* "if ( self )" 구문은 "if ( self != nil )" 과 동일하며, 부모의 생성자가 제대로된 객체를 리턴했는지 확인할 때 사용한다. nil 은 C++의 NULL에 대응하는 Objective-C 의 개념이다. 
* 자신의 변수를 초기화한 뒤 자기 자신을 리턴하는 것에 유의하자. 
* 기본 생성자는 "-(id) init;" 이다. 
* Objective-C 에서 생성자는 기술적으로 볼 때 단순히 초기화 메소드에 불과하다. C++ 이나 자바의 생성자 개념과는 다르다. 


접근 권한 제어

* 기본 접근권한은 @protected 이다. 
* 자바에서는 각 변수나 메소드앞에 public/private/protected 를 선언하지만, Objective-C 에서는 C++의 인스턴스 변수에 대한 접근권한 설정과 유사한 방식을 사용한다.  




* AccPvd 프로젝트를 만들고 Access.h 유니트 추가. 

#import <Foundation/NSObject.h>

@interface Access: NSObject {
@public
  int publicVar;
@private
  int privateVar;
  int privateVar2;
@protected
  int protectedVar;
}
@end


* Access.m 유니트 추가. 

#import "Access.h"

@implementation Access
@end


* main.m 유니트 추가. 

#import "Access.h"
#import <stdio.h>

int main ( int argc, const char *argv[] ) {
  Access *a = [[Access alloc] init];
  
  // works
  a->publicVar = 5;
  printf( "public var: %i\n", a->publicVar );
  
  // doesn't compile
  //a->privateVar = 10;
  //printf( "private var: %i\n", a->privateVar );
  
  [a release];
  
  return 0;
}


* 출력 결과는 다음과 같다. 

public var: 5


* C++의 private: [list of vars] public: [list of vars] 구문과 마찬가지 역할을 하는 것을 알 수 있다. 


클래스 레벨의 메소드 사용

* 때때로 인스턴스가 아닌 클래스 단위의 변수와 함수가 유용할 때가 있다. 클래스가 몇 번이나 인스턴스화 되었는지 추적하고 싶을 때가 좋은 예가 될 것이다. 




* ClassLvlAcc 프로젝트를 만들고 ClassA.h 추가. 

#import <Foundation/NSObject.h>

static int count;

@interface ClassA: NSObject
+(int) initCount;
+(void) initialize;
@end


* ClassA.m 추가.

#import "ClassA.h"

@implementation ClassA
-(id) init {
  self = [super init];
  count++;
  return self;
}

+(int) initCount {
  return count;
}  
  
+(void) initialize {
  count = 0;
}

@end


* main.m 은 다음과 같이. 

#import "ClassA.h"
#import <stdio.h>

int main ( int argc, const char *argv[] ) {
  ClassA *c1 = [[ClassA alloc] init];
  ClassA *c2 = [[ClassA alloc] init];
  
  // print count
  printf( "ClassA count: %i\n", [ClassA initCount] );
  
  ClassA *c3 = [[ClassA alloc] init];
  
  // print count again
  printf( "ClassA count: %i\n", [ClassA initCount] );
  
  [c1 release];
  [c2 release];
  [c3 release];
  
  return 0; 
}


* 출력결과는 다음과 같다. 

ClassA count: 2
ClassA count: 3


* "static int count = 0;" 여기서 클래스의 변수를 정의하고 있지만 바람직한 방식은 아니다. 자바의 static class variable 처럼 되어야 하겠지만 일단 동작은 하니 넘어가자. 
* "+(int) initCount;" 이 메소드가 생성횟수를 리턴하는 역할을 한다. 앞부분의 스코프가 지금까지 쓰던 "-" 가 아닌 "+"인 것에 유의하자. "+"가 사용된 함수는 클래스 레벨의 함수임을 의미한다. 
* 변수 접근은 멤버변수와 마찬가지이다. ClassA의 생성자에서 "count++" 을 호출해 값을 증가시키고 있다. 
* "+(void) initialize" 메소드는 프로그램이 구동될 때 자동으로 호출된다. 따라서 클래스 레벨의 변수를 초기화하기 딱 좋은 위치이다. 


예외 처리


문제가 해결될 떄 까지 이 챕터는 일단 봉인. 


상속, 다형성, 그리고 다른 OOP 기능들

객체에 대한 만능 자료형 "id"

* Objective-C 는 id 라고 부르는 자료형이 있다. 이 물건은 여러모로 void* 와 유사하지만 반드시 객체에 대해서만 사용된다. Objective-C 에서는 C++이나 자바와 달리 객체의 메소드를 호출할 때 그 자료형을 몰라도 관계없다. 단지 메소드만 있으면 된다. 이를 객체에 대한 메시지 전달이라고 한다. 

* idTypeTest 프로젝트를 만들고 생성자에서 사용한 예제에서 Fraction.h, Fraction.m 을 그대로 가지고 온다. 




* Complex.h

#import <Foundation/NSObject.h>

@interface Complex: NSObject {
  double real;
  double imaginary;
}
-(Complex*) initWithReal: (double) r andImaginary: (double) i;
-(void) setReal: (double) r;
-(void) setImaginary: (double) i;
-(void) setReal: (double) r andImaginary: (double) i;
-(double) real;
-(double) imaginary;
-(void) print;
@end


* Complex.m

#import "Complex.h"
#import <stdio.h>

@implementation Complex
-(Complex*) initWithReal: (double) r andImaginary: (double) i {
  self = [super init];
  
  if (self) {
    [self setReal: r andImaginary: i];
  }
  
  return self;
}

-(void) setReal: (double) r {
  real = r;
}

-(void) setImaginary: (double) i {
  imaginary = i;
}

-(void) setReal: (double) r andImaginary: (double) i {
  real = r;
  imaginary = i;
}

-(double) real {
  return real;
}

-(double) imaginary {
  return imaginary;
}

-(void) print {
  printf( "%f + %fi", real, imaginary );
}

@end


* main.m

#import <stdio.h>
#import "Fraction.h"
#import "Complex.h"

int main (int argc, const char *argv[]) {
  // 인스턴스 생성
  Fraction *frac = [[Fraction alloc] initWithNumerator: 1 denominator: 10];
  Complex *comp = [[Complex alloc] initWithReal: 10 andImaginary: 15];
  id number;
  
  // fraction 출력. 
  number = frac;
  printf( "The fraction is: " );
  [number print];
  printf( "\n" );
  
  // compex 출력
  number = comp;
  printf( "The complex number is: " );
  [number print];
  printf( "\n" );
  
  // 메모리 해제. 
  [frac release];
  [comp release];
  
  return 0;
}


* 출력 결과

The fraction is: 1/10
The complex number is: 10.000000 + 15.000000i


* 이러한 동적 형 연결은 어떤 함수를 호출하기 위해 그 객체의 형을 알 필요가 없다는 큰 장점이 있다. 객체가 메시지에 반응한다면, 그 메소드를 실행될 것이다. 자바에서는 .intValue() 라는 함수를 호출할 경우 먼저 캐스팅을 하고 그 뒤에 메소드를 호출해야 하지만, Objective-C 에서는 이런 지저분한 캐스팅문제로 머리아플 필요가 없다. 


상속 (Inheritance)

* 원문의 예제는 Rectangle 객체를 만들게 되어있는데, MinGW에서는 wingdi.h 에 정의된 Rectangle 함수와 충돌이 나므로 MyRectangle 로 변경하였다. 
* Inheritance 프로젝트를 만들고 다음과 같이 각 유니트를 코딩한다. 




* Rectangle.h

#import <Foundation/NSObject.h>

@interface MyRectangle: NSObject {
  int width;
  int height;
}

-(MyRectangle*) initWithWidth: (int) w height: (int) h;
-(void) setWidth: (int) w;
-(void) setHeight: (int) h;
-(void) setWidth: (int) w height: (int) h;
-(int) width;
-(int) height;
-(void) print;
@end


* Rectangle.m

#import "Rectangle.h"
#import <stdio.h>

@implementation MyRectangle
-(MyRectangle*) initWithWidth: (int) w height: (int) h {
  self = [super init];
  
  if (self) {
    [self setWidth: w height: h];
  }
  
  return self;
}

-(void) setWidth: (int) w {
  width = w;
}

-(void) setHeight: (int) h {
  height = h;
}

-(void) setWidth: (int) w height: (int) h {
  width = w;
  height = h;
}

-(int) width {
  return width;
}

-(int) height {
  return height;
}

-(void) print {
  printf( "width = %i, height = %i", width, height );
}
@end


* Square.h

#import "Rectangle.h"

@interface MySquare: MyRectangle
-(MySquare*) initWithSize: (int) s;
-(void) setSize: (int) s;
-(int) size;
@end


* Square.m

#import "Square.h"

@implementation MySquare

-(MySquare*) initWithSize: (int) s {
  self = [super init];
  
  if (self) {
    [self setSize: s];
  }
  
  return self;
}

-(void) setSize: (int) s {
  width = s;
  height = s;
}

-(int) size {
  return width;
}

// 오버라이딩. 
-(void) setWidth: (int) w {
  [self setSize: w];
}

-(void) setHeight: (int) h {
  [self setSize: h];
}


@end


* main.m

#import "Square.h"
#import "Rectangle.h"
#import <stdio.h>

int main (int argc, const char *argv[]) {
  MyRectangle *rec = [[MyRectangle alloc] initWithWidth: 10 height: 20];
  MySquare *sq = [[MySquare alloc] initWithSize: 15];
  
  // 각 사각형 객체 출력
  printf( "Rectangle: ");
  [rec print];
  printf( "\n" );
  
  printf( "Square: ");
  [sq print];
  printf( "\n" );
  
  // 정사각형 정보 변경
  [sq setWidth: 20];
  printf( "Square after change: " );
  [sq print];
  printf( "\n" );
  

  // 메모리 해제. 
  [rec release];
  [sq release];
  
  return 0;
}


* 출력 결과

Rectangle: width = 10, height = 20
Square: width = 15, height = 15
Square after change: width = 20, height = 20


* Objective-C 의 상속은 자바와 유사하다. 부모 클래스를 확장할 때는 (상속도 단일상속만 가능) 부모의 메소드를 단순히 구현부에 재정의 하는 것으로 오버라이딩 할 수 있다.  C++ 에서의 가상테이블은 잊어버리자.   
* 한가지만 짚고 넘어가자. 만약 정사각형의 생성자 호출 부분에서 MySquare *sq = [[MySquare alloc] initWithWidth: 10 height: 15] 와 같이 부모의 생성자를 호출하면 어떻게 될까. 답은 컴파일 에러가 발생하게 된다. 왜냐하면 MyRectangle 클래스 생성자가 반환하는 값은 MyRectangle* 이지 MySqare* 는 아니기 때문이다. id 객체는 이럴 때 써먹을 수 있다. 만약 하위 클래스에서 상위 클래스의 생성자를 사용하려면 단순히 리턴값을 MyRectangle* 이 아닌 id 로 바꾸기만 하면 된다. 



동적 자료형 (Dynamic types)

* 동적 자료형에서 사용할 수 있는 Objective-C 의 메소드들을 살펴보자. 

 -(BOOL) isKindOfClass: classObj  이 객체가 clssObj 의 멤버 또는 하위클래스인지 여부
 -(BOOL) isMemberOfClass: classObj  이 객체가 classObj 의 멤버인지 여부
 -(BOOL) isRespondsToSelector: selector  이 객체가 selector 라는 이름의 메소드를 가지고 있는지
 +(BOOL) instancesRespondToSelector: selector  이 클래스에서 생성된 객체가 지정된 selector에 반응하는지
 -(id) performSelector: selector;  객체의 지정된 셀렉터를 실행


* NSObject 에서 상속된 모든 객체는 클래스 객체를 반환하는 class 라는 메소드를 가진다. 이 방식은 자바의 getClass() 메소드와 아주 유사하다. 위에 나열된 메소드들은 이 클래스 객체를 사용한다.  
* Objective-C 에서 셀렉터(selector)는 메시지를 표현하기 위해 사용된다. 셀렉터를 구현하는 문법은 아래를 참고하자. 

* 앞의 예제에서 main.m 을 아래와 같이 수정한다. 

#import "Square.h"
#import "Rectangle.h"
#import <stdio.h>

int main (int argc, const char *argv[]) {
  MyRectangle *rec = [[MyRectangle alloc] initWithWidth: 10 height: 20];
  MySquare *sq = [[MySquare alloc] initWithSize: 15];

  // isMemberOfClass
  
  // true
  if ( [sq isMemberOfClass: [MySquare class]] == YES ) {
    printf( "square is member of square class\n" );
  }
  
  // false
  if ( [sq isMemberOfClass: [MyRectangle class]] == YES ) {
    printf( "square is member of rectangle class\n" );
  }
  
  // flase
  if ( [sq isMemberOfClass: [NSObject class]] == YES ) {
    printf( "square is member of object class\n" );
  }
  
  // isKindOfClass
  
  // true
  if ( [sq isKindOfClass: [MySquare class]] == YES ) {
    printf( "square is kind of square class\n" );
  }
  
  // true
  if ( [sq isKindOfClass: [MyRectangle class]] == YES ) {
    printf( "square is kind of rectangle class\n" );
  }
  
  // true
  if ( [sq isKindOfClass: [NSObject class]] == YES ) {
    printf( "square is kind of object class\n" );
  }
  
  
  // respondsToSelector
  
  // true
  if ( [sq respondsToSelector: @selector( setSize: )] == YES ) {
    printf( "square responds to setSize: method\n" );
  }
  
  // flase
  if ( [sq respondsToSelector: @selector( nonExistant )] == YES ) {
    printf( "square responds to nonExistant method\n" );
  }

  // false
  if ( [sq respondsToSelector: @selector( alloc )] == YES ) {
    printf( "square responds to alloc method\n" );
  }
  
  // true 
  if ( [MySquare respondsToSelector: @selector( alloc )] == YES ) {
    printf( "square class responds to alloc method\n" );
  }
  
  // instancesRespondToSelector
  
  // false
  if ( [MyRectangle instancesRespondToSelector: @selector( setSize: )] == YES ) {
    printf( "rectangle instance responds to setSize: method\n" );
  }
  
  // true
  if ( [MySquare instancesRespondToSelector: @selector( setSize: )] == YES ) {
    printf( "square instance responds to setSize: method\n" );
  }
    
  // 메모리 해제. 
  [rec release];
  [sq release];
  
  return 0;
}


* 출력 결과는 다음과 같다. 

square is member of square class
square is kind of square class
square is kind of rectangle class
square is kind of object class
square responds to setSize: method
square class responds to alloc method
square instance responds to setSize: method



카테고리 (Categories)

* 메소드를 클래스에 추가할 때 일반적으로는 확장을 하게 되지만, 이 방법이 통하지 않는 경우가 있다. 특히 소스코드가 없는 경우가 그럴 것이다. 카테고리를 사용하면 기존에 존재하는 클래스를 확장하지 않고 원하는 함수를 추가할 수 있다. 루비가 이와 비슷한 기능을 제공한다. 

* Categories 프로젝트를 만들고 앞의 예에서 컴파일된 Fraction.o 파일과 Fraction.h 파일을 동일한 위치에 복사한다. 




* FractionMath.h 

#import "Fraction.h"
@interface Fraction (Math)
-(Fraction*) add: (Fraction*) f;
-(Fraction*) mul: (Fraction*) f;
-(Fraction*) div: (Fraction*) f;
-(Fraction*) sub: (Fraction*) f;
@end


* FractionMath.m

#import "FractionMath.h"

@implementation Fraction (Math) 
-(Fraction*) add: (Fraction*) f {
  return [[Fraction alloc] initWithNumerator: numerator * [f denominator] +
                                              denominator * [f numerator] 
                           denominator: denominator * [f denominator]];

-(Fraction*) mul: (Fraction*) f {
  return [[Fraction alloc] initWithNumerator: numerator * [f numerator]
                           denominator: denominator * [f denominator]];
}

-(Fraction*) div: (Fraction*) f {
  return [[Fraction alloc] initWithNumerator: numerator * [f denominator]
                           denominator: denominator * [f numerator]];
}

-(Fraction*) sub: (Fraction*) f {
  return [[Fraction alloc] initWithNumerator: numerator * [f denominator] -
                                              denominator * [f numerator]
                           denominator: denominator * [f denominator]];
}                                              
@end


* main.m

#import <stdio.h>
#import "Fraction.h"
#import "FractionMath.h"

int main( int argc, const char *argv[] ) {
  // 새 인스턴스 생성. 
  Fraction *frac1 = [[Fraction alloc] initWithNumerator: 1 denominator: 3];
  Fraction *frac2 = [[Fraction alloc] initWithNumerator: 2 denominator: 5];
  Fraction *frac3 = [frac1 mul: frac2];
  
  // 출력. 
  [frac1 print];
  printf( " * " );
  [frac2 print];
  printf( " = " );
  [frac3 print];
  printf( "\n" );
  
  // 메모리 해제
  [frac1 release];
  [frac2 release];
  [frac3 release];
 
  return 0;
}
  

* 이대로 빌드하면   [Linker error] undefined reference to `__objc_class_name_Fraction'  라는 에러가 발생한다. Fraction.o 파일이 링크되지 않았기 때문이다. Dev-C++ 의 프로젝트 옵션의 링커에 Fraction.o 를 추가해도 이 항목이 LINKLIB 쪽으로 가기 때문에 제대로 되지 않는다. 이 버그는 차차 고쳐보도록 하고, 일단 빌드를 위해 생성되는 Makefile.win 을 열고 LINKOBJ 항목에 Fraction.o 를 직접 추가한다. 

......
LINKOBJ  = Fraction.o FractionMath.o main.o $(RES)
......


* 이제 make 를 사용해 빌드해보자. 

C:\GNUstep\bin\make.exe -f Makefile.win


* 출력결과는 

1/3 * 2/5 = 2/15


* Fraction.m 소스코드 없이도 Fraction 클래스에 새로운 메소드를 추가할 수 있다. 
* 이 마술의 비밀은 바로 2개의 @implementation 과 @interface 에 있다. @interface Fraction (Math) 그리고 @implementation Fraction (Math)
* 동일한 이름으로는 오직 한개의 카테고리만을 만들 수 있다. 카테고리를 추가하려면 이전과 다른 유일한 이름으로 만들어야 한다. 
* 카테고리에서는 인스턴스 변수를 추가할 수 없다. 
* 카테고리는 private 메소드를 만들 때 유용하다. Objective-C 는 자바와 달리 private/protected/public 메소드에 대한 구분이 명확하지 않지만, 감추고 싶은 메소드를 만들 때에는 카테고리를 사용할 수 있다.  예를 들어 클래스의 private 메소드들을 클래스 헤더파일이 아닌 소스파일에 정의하는 것이다. 다음 예를 보자.  

* MyClass.h

#import <Foundation/NSObject.h>

@interface MyClass: NSObject
-(void) publicMethod;
@end


* MyClass.m

#import "MyClass.h"
#import <stdio.h>

@implementation MyClass
-(void) publicMethod {
  printf( "Public method\n" );
}
@end

// private method
@interface MyClass (Private)
-(void) privateMethod;
@end

@implementation MyClass (Private)
-(void) privateMethod {
  printf( "Private method\n" );
}
@end


* main.m

#import "MyClass.h"

int main ( int argc, const char *argv[] ) {
  MyClass *obj = [[MyClass alloc] init];
  
  // 컴파일 되는 부분.
  [obj publicMethod];
  
  // 컴파일 안되는 부분
  //[obj privateMethod];
  
  [obj release];
 
  return 0;
}


* 결과

Public method



포징 (Posing)

* 포징은 카테고리와 유사하지만 조금 더 꼬여있다. 포즈를 이용하면 클래스를 확장한 서브클래스를 수퍼클래스와 전역적으로 바꿔칠 수 있다. NSArray를 확장한 NSArrayChild를 만들었다고 할 때, NSArrayChild를 NSArray로 포징하면 코드 내의 NSArray는 자동으로 NSArrayChild로 변경되게 된다. 

* Posing 프로젝트를 만들고 이전에 만든 Fraction.h, Fraction.m 파일을 옮겨온다. 그리고 다음 유니트들을 추가하자. 

* FractionB.h

#import "Fraction.h"

@interface FractionB: Fraction
-(void) print;
@end


* FractionB.m

#import "FractionB.h"
#import <stdio.h>

@implementation FractionB
-(void) print {
  printf( "(%i/%i)", numerator, denominator );
}
@end


* main.m

#import <stdio.h>
#import "Fraction.h"
#import "FractionB.h"

int main ( int argc, const char *argv[] ) {
  Fraction *frac = [[Fraction alloc] initWithNumerator: 3 denominator: 10];
  
  // 출력
  printf( "The fraction is: " );
  [frac print];
  printf( "\n" );
  
  // FractionB 가 Fraction인양 폼을 잡을 수 있도록 한다. (Pose)
  [FractionB poseAsClass: [Fraction class]];
  
  Fraction *frac2 = [[Fraction alloc] initWithNumerator: 3 denominator: 10];
  
  // 출력
  printf( "The fraction is: " );
  [frac2 print];
  printf( "\n" );

  // 메모리 해제
  [frac release];
  [frac2 release];
  
  return 0;
}


* 결과

The fraction is: 3/10
The fraction is: (3/10)


* 출력결과를 살펴보면 처음에는 "3/10", 그리고 두번째는 FractionB 에 구현된 "(3/10)" 이 표시된다.  
* poseAsClass 는 서브클래스가 수퍼클래스인양 행세할 수 있도록 하는 NSObject의 메소드이다. ("폼잡도록 하는" 정도로 이해하면 될 듯)



프로토콜 (Protocols)

* Objective-C의 프로토콜은 자바의 인터페이스나 C++의 순수가상함수와 기능적으로 동일하다. 
* Protocol 프로젝트를 시작하고 idTypeTest 프로젝트에서 Fraction.h/m, Complex.h/m 을 복사한다. 이제 다음과 같이 코딩해보자. 

* Printing.h

@protocol Printing
-(void) print;
@end


* Fraction.h

#import <Foundation/NSObject.h>
#import "Printing.h"

@interface Fraction: NSObject <Printing, NSCopying> {
  int numerator;
  int denominator;                      
}

-(Fraction*) initWithNumerator: (int) n denominator: (int) d;
-(void) print;
-(void) setNumerator: (int) n;
-(void) setNumerator: (int) n andDenominator: (int) d;
-(void) setDenominator: (int) d;
-(int) numerator;
-(int) denominator;
@end;


* Fraction.m

#import "Fraction.h"
#import <stdio.h>

@implementation Fraction

-(Fraction*) initWithNumerator: (int) n denominator: (int) d {
  self = [super init];
  if (self) {
    [self setNumerator: n andDenominator: d];
  }             
  
  return self;
}


-(void) print {
  printf( "%i/%i", numerator, denominator );
}

-(void) setNumerator: (int) n {
  numerator = n;
}
  
-(void) setNumerator: (int) n andDenominator: (int) d {
  numerator = n;
  denominator = d;
}

-(void) setDenominator: (int) d {
  denominator = d;
}

-(int) denominator {
  return denominator;
}

-(int) numerator {
  return numerator;
}

-(id) copyWithZone: (NSZone*) zone {
  return [[Fraction allocWithZone: zone] initWithNumerator: numerator
                                         denominator: denominator]; 
}
@end

  원문에서는 (Fraction*) copyWithZone... 으로 되어있으나 컴파일에러가 나서 (id) ... 로 수정. 


* Complex.h

#import <Foundation/NSObject.h>
#import "Printing.h"

@interface Complex: NSObject <Printing> {
  double real;
  double imaginary;
}
-(Complex*) initWithReal: (double) r andImaginary: (double) i;
-(void) setReal: (double) r;
-(void) setImaginary: (double) i;
-(void) setReal: (double) r andImaginary: (double) i;
-(double) real;
-(double) imaginary;
//-(void) print;
@end


* Complex.m

  - 변화 없음. 그대로 사용. 


* main.m

#import <stdio.h>
#import "Fraction.h"
#import "Complex.h"

int main (int argc, const char *argv[]) {
  // 인스턴스 생성
  Fraction *frac = [[Fraction alloc] initWithNumerator: 3 denominator: 10];
  Complex *comp = [[Complex alloc] initWithReal: 5 andImaginary: 15];
  id <Printing> printable;
  id <NSCopying, Printing> copyPrintable;
  
  // frac 출력. 
  printable = frac;
  printf( "Thr fraction is: " );
  [printable print];
  printf( "\n" );
  
  // comp 출력
  printable = comp;
  printf( "The complex number is: " );
  [printable print];
  printf( "\n" );
  
  // 컴파일되는 코드. Fraction이 Printing과 NSCopyable을 모두 따르기 때문에..
  copyPrintable = frac;
  
  // 컴파일불가. Complex는 Printing 만 따르기 때문이다. 
  //copyPrintable = comp;
  
  // 프로토콜을 따르는지 테스트.
  
  // true
  if ( [frac conformsToProtocol: @protocol( NSCopying )] == YES ) {
    printf( "Fraction conforms to NSCopying\n" );
  }
  
  // false
  if ( [comp conformsToProtocol: @protocol( NSCopying )] == YES ) {
    printf( "Complex conforms to NSCopying\n" );
  }
  
  
  // 메모리 해제
  [frac release];
  [comp release];

  return 0;
}
  


* 출력 결과

Thr fraction is: 3/10
The complex number is: 5.000000 + 15.000000i
Fraction conforms to NSCopying


* 프로토콜 문법은 간단히 "@protocol 프로토콜명 (반드시 정의해야하는 메소드들) @end" 와 같이 작성한다. 
* 프로토콜을 따르게 하기 위해서는 해당 프로토콜을 <> 안에 넣고 쉼표로 구분한다. 
  예: @interface SomeClass <Protocol1, Protocol2, Protocol3>
* 프로토콜을 구현한 메소드들은 헤더의 메소드리스트에 없어도 관계없다. 위의 예제에 보이듯 Complex.h 에는 "-(void) print" 에 대한 정의가 없다. 하지만 구현부에는 존재해야 한다. 
* Objective-C 에서 특이한 점은 형을 지정하는 방식이다. 자바나 C++ 에서는 Printing *someVar = (Printing*) frac; 과 같이 명시적으로 형을 표현해야 하지만, Objective-C 는 id형과 프로토콜을 사용해 id <Printing> var = frac; 처럼 표현한다. 이를 통해 여러개의 프로토콜이 필요한 자료형을 하나의 변수를 사용해 동적으로 명시할 수 있다. 위 코드의 id <Printing, NSCopying> var = frac; 이 좋은 예이다. 
* 객체의 상속을 테스트하기위해 @selector를 사용했던 것과 마찬가지로, 인터페이스가 일치하는지 살피기 위해서는 @protocol을 사용한다. [object conformsToProtocol: @protocol(SompProtocol)] 은 객체가 프로토콜을 따르는지 여부를 BOOL형으로 반환하는 함수이다. 클래스의 경우에도 마찬가지로 [SomeClass conformsToProtocol: @protocol(SomeProtocol)] 처럼 쓸 수 있다. 



메모리 관리

* 지금까지는 Objective-C 의 메모리관리에 대해 별다른 설명을 하지 않았다. 물론 단순히 dealloc 하면 메모리는 해제되겠지만, 만약 다른 객체가 이 객체를 참조하고 있다면 어떻게 해야할까. 이제부터는 이런 상황에 대한 주의와 함께 Foundation 프레임워크에서 클래스를 만들 때 어떻게 메모리를 관리하는지에 대한 설명을 시작하려 한다. 
* 당연한 이야기지만, 위의 예제들은 모두 적절히 메모리가 관리되었다. 


Retain과 Release

* NSObject에서 상속받은 모든 객체는 retain과 release라는 두개의 메소드를 가진다. 모든 객체는 내부의 카운터를 통해 자신이 얼마나 참조되었는지를 추적하고 있다. 만약 3번의 참조되고 있다면 자기 자신을 dealloc 하면 안될것이다. 물론 참조갯수가 0이라면 메모리에서 해제되어도 된다. [object retain] 은 이 참조횟수, 즉 레퍼런스카운트를 하나 증가시키고 [object release]는 하나 감소시킨다. 만약 [object release] 에 의해 카운터가 0이 되면 dealloc 이 호출된다. 

* ReleaseTest 프로젝트르 시작하고 idTypeTest에 사용된 Fraction.h/m 파일을 복사한다. 
* Fraction.m 에 다음과 같이 dealloc 메소드를 구현한다. 

......
-(void) dealloc {
  printf( "Deallocing fraction\n" );
  [super dealloc];
}
......


* main.m

#import "Fraction.h"
#import <stdio.h>

int main ( int argc, const char *argv[] ) {
  Fraction *frac1 = [[Fraction alloc] init];
  Fraction *frac2 = [[Fraction alloc] init];
  
  // 현재 레퍼런스카운트 출력.
  printf( "Fraction 1 retain count: %i\n", [frac1 retainCount] );
  printf( "Fraction 2 retain count: %i\n", [frac2 retainCount] );

  // 레퍼런스카운트 증가
  [frac1 retain]; // 2
  [frac1 retain]; // 3
  [frac2 retain]; // 2
  
  // 현재 레퍼런스카운트 출력.
  printf( "Fraction 1 retain count: %i\n", [frac1 retainCount] );
  printf( "Fraction 2 retain count: %i\n", [frac2 retainCount] );
  
  // 레퍼런스카운트 감소
  [frac1 release]; // 2
  [frac2 release]; // 1

  // 현재 레퍼런스카운트 출력.
  printf( "Fraction 1 retain count: %i\n", [frac1 retainCount] );
  printf( "Fraction 2 retain count: %i\n", [frac2 retainCount] );
  
  [frac1 release]; // 1
  [frac1 release]; // 0
  [frac2 release]; // 0
  
  return 0;
}


* 출력 결과

Fraction 1 retain count: 1
Fraction 2 retain count: 1
Fraction 1 retain count: 3
Fraction 2 retain count: 2
Fraction 1 retain count: 2
Fraction 2 retain count: 1
Deallocing fraction
Deallocing fraction


* retain 호출은 카운터를 증가시키고 release 호출은 감소시킨다. [obj retainCount] 는 레퍼런스 카운트를 정수값으로 반환한다. 이 값이 0이 되면 객체는 dealloc 메소드를 호출하고 자기자신의 메모리를 해제한다. 마지막줄의 "Deallocing fraction" 은 그것을 나타낸다. 



Dealloc

* 객체가 다른 객체를 포함할 경우, 자기 자신을 제거하기 전에 포함한 객체도 메모리에서 제거해야 한다. Objective-C 의 또다른 장점은 nil 에도 메시지를 전달할 수 있기 때문에 객체 제거시 귀찮은 에러검사를 생략할 수 있다는 것이다. 

* DeallocTest 프로젝트를 만들고 다음과같이 코딩하자. 

* AddressCard.h

#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>

@interface AddressCard: NSObject {
  NSString *first;
  NSString *last;
  NSString *email;
}
-(AddressCard*) initWithFirst: (NSString*) f
                last: (NSString*) l
                email: (NSString*) e;
-(NSString*) first;
-(NSString*) last;
-(NSString*) email;
-(void) setFirst: (NSString*) f;
-(void) setLast: (NSString*) l;
-(void) setEmail: (NSString*) e;
-(void) setFirst: (NSString*) f
        last: (NSString*) l
        email: (NSString*) e;
-(void) setFirst: (NSString*) f last: (NSString*) l;
-(void) print;
@end                       


* AddressCard.m

#import "AddressCard.h"
#import <stdio.h>

@implementation AddressCard
-(AddressCard*) initWithFirst: (NSString*) f
                last: (NSString*) l
                email: (NSString*) e {
  self = [super init];
  
  if (self) {
    [self setFirst: f last: l email: e];
  }
  
  return self;
}

-(NSString*) first {
  return first;
}

-(NSString*) last {
  return last;
}
                
-(NSString*) email {
  return email;
}

-(void) setFirst: (NSString*) f {
  [f retain];
  [first release];
  first = f;
}                

-(void) setLast: (NSString*) l {
  [l retain];
  [last release];
  last = l;
}

-(void) setEmail: (NSString*) e {
  [e retain];
  [email release];
  email = e;
}

-(void) setFirst: (NSString*) f
        last: (NSString*) l
        email: (NSString*) e {
  [self setFirst: f];
  [self setLast: l];
  [self setEmail: e];
}

-(void) setFirst: (NSString*) f last: (NSString*) l {        
  [self setFirst: f];
  [self setLast: l];
}

-(void) print {
  printf( "%s %s <%s>", [first cString], [last cString], [email cString] );
}

-(void) dealloc {
  [first release];
  [last release];
  [email release];
  
  [super dealloc];
}

@end


* main.m

#import "AddressCard.h"
#import <Foundation/NSString.h>
#import <stdio.h>

int main ( int argc, const char *argv[] ) {
  //NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  
  // 문자열 할당. 
  NSString *first = [[NSString alloc] initWithCString: "Tom"];
  NSString *last = [[NSString alloc] initWithCString: "Jones"];
  NSString *email = [[NSString alloc] initWithCString: "tom@jones.com"];
  
  AddressCard *tom = [[AddressCard alloc] initWithFirst: first
                                           last: last
                                           email: email];
                                           
  // 문자열들은 AddressCard 내에서 retain 되면서 참조갯수가 2로 됨.
  // 여기서는 필요없으므로 release. 
  [first release];
  [last release];
  [email release];
  
  // 참조횟수 출력. 
  printf( "Retain count: %i\n", [[tom first] retainCount] );
  [tom print];
  printf( "\n" );

  // 메모리 해제. 
  [tom release];
  
  return 0;  
}


* 출력 결과

Retain count: 1
2010-11-26 18:26:07.890 DeallocTest[4036] autorelease called without pool for object (b58c90) of class GSAutoreleasedMemory in thread <NSThread: 0x3dea0>
2010-11-26 18:26:07.890 DeallocTest[4036] autorelease called without pool for object (b58f18) of class GSAutoreleasedMemory in thread <NSThread: 0x3dea0>
2010-11-26 18:26:07.890 DeallocTest[4036] autorelease called without pool for object (bc3e40) of class GSAutoreleasedMemory in thread <NSThread: 0x3dea0>
Tom Jones <tom@jones.com>

밑에도 나오지만 맥의 코코아에서는 Autorelease pool 이 기본설정된단다. 결과가 원문과 다르게 NSLog로 도배된 이유는, NSString 의 cString 메소드를 호출할 때 autorelease 가 호출되지만 지금 학습중인 환경에서는 Autorelease pool 을 쓰고 있지 않기 때문이다. 따라서 다음장에서 살펴볼 NSAutolreleasePool 을 사용하면 이 로그가 사라지게 된다. 

main.m 에서 #import <Foundation/NSAutoreleasePool.h> 를 상단에 추가하고 NSString 객체 생성 전에 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 해주면 저 로그가 사라진다. 이 pool 은 가장 마지막에 [pool release] 해주어야 한다. 

Retain count: 1
Tom Jones <tom@jones.com>


* AddressCard.m 는 멤버변수 해제를 위해 dealloc 메소드를 작성하는 방법에 대해 보여주고 있다. 
* 각각의 set 메소드에서 수행하는 세 코드는 그 순서가 매우 중요하다. 좀 이상한 예이지만 set 함수에 자기 자신의 인자를 넘기는 경우를 상상해보자. 만약 release 를 먼저하고 그 다음에 retain 을 한다면 자기 자신은 메모리에서 사라져버리게 된다. 때문에 항상 1) retain 2) release 3) 값설정 의 순서를 지켜야 한다. 
* C문자열은 유니코드를 지원하지 않기 때문에, 문자열 변수를 초기화할 때 일반적으로는 C문자열을 사용하지 않는다. NSAutoreleasePool을 사용하는 다음 예제에서 좀 더 적절한 방법에 대해 이야기 할 것이다. 
* 이 방법은 멤버 변수 메모리를 관리하는 여러방법 중 하나일 뿐이다. 또 다른 방법으로는 set 메소드 안에서 값을 복사하는 방법이 있다. 



Autorelease Pool

* NSString 을 비롯한 Foundation 프레임워크를 사용하려면 좀 더 유연한 메모리관리가 필요하다. 이 시스템은 Autorelease pool 을 사용한다. 
* 맥에서 코코아 어플리케이션을 개발할 때는 이 Autorelease pool 이 자동으로 설정된다. 

* AutoPool 프로젝트를 시작하고 다음과 같이 코딩하자. 

* main.m

#import <Foundation/NSString.h>
#import <Foundation/NSAutoreleasePool.h>
#import <stdio.h>

int main (int argc, const char *argv[] ) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  
  NSString *str1 = @"constant string";
  NSString *str2 = [NSString stringWithString: @"string managed by pool"];
  NSString *str3 = [[NSString alloc] initWithString: @"self managed string"];
 
  // 상태 출력
  printf( "%s retain count: %x\n", [str1 cString], [str1 retainCount] );
  printf( "%s retain count: %x\n", [str2 cString], [str2 retainCount] );
  printf( "%s retain count: %x\n", [str3 cString], [str3 retainCount] );
  
  // 메모리 해제
  [str3 release];
  
  // 풀 해제
  [pool release];
  
  return 0;
}
  

* 출력 결과

constant string retain count: 676e6973
string managed by pool retain count: 1
self managed string retain count: 1

 원문에서는 ffffffff 이 나온다고 하는데 좀 다르다.  


* 눈에 띄는 점을 살펴보자. 먼저 str1의 카운터가 ffffffff 이다. 
* 또 한가지는 str3만 release 했는데도 이 프로그램에서 메모리관리는 완벽히 이루어지고 있다는 것이다. 첫번째 상수 문자열은 자동으로 Autorelease pool 에 추가되었고, 다른 문자열이 사용한 stringWithString 메소드는 NSString 문자열을 만들면서 자동으로 Autorelease pool에 이 문자열을 추가하기 때문이다. 
* 적절한 메모리 관리를 위해 이 점을 잊지 말자. [NSString stringWithString: @"String"] 같은 편의함수들은 Autorelease pool 을 사용하는 반면 [[NSString alloc] init] 처럼 직접 메모리를 할당해서 만든 함수들은 Autorelease pool 을 사용하지 않는다는 점이다. 
* Objective-C 에서는 두가지 메모리관리 방법을 사용한다. 1) retain 과 release 또는 2) retain 과 release/autorelease
* 각 retain 에는 반드시 한 개의 release 또는 autorelease 가 있어야 한다. 

* 예제를 통해 이 내용을 살펴보자. 
* AutoPool2 프로젝트를 생성하고 idTypeTest의 Fraction.h, Fraction.m 을 복사해온다. 

* Fraction.h 에 다음 내용을 추가한다. 

......
+(Fraction*) fractionWithNumerator: (int) n denominator: (int) d;
......


* Fraction.m 에 구현부를 만들어준다. 

......
+(Fraction*) fractionWithNumerator: (int) n denominator: (int) d {
  Fraction *ret = [[Fraction alloc] initWithNumerator: n denominator: d];
  [ret autorelease];
  
  return ret;
}
......


* main.m

#import <Foundation/NSAutoreleasePool.h>
#import "Fraction.h"
#import <stdio.h>

int main ( int argc, const char *argv[] ) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  Fraction *frac1 = [Fraction fractionWithNumerator: 2 denominator: 5];
  Fraction *frac2 = [Fraction fractionWithNumerator: 1 denominator: 3];
  
  // frac1 출력. 
  printf( "Fraction 1: " );
  [frac1 print];
  printf( "\n" );
  
  // frac2 출력. 
  printf( "Fraction 2: " );
  [frac1 print];
  printf( "\n" );
  
  // 이 객체의 직접해제는 segmentation fault 에러를 발생시킨다.
  //[frac1 release];
  
  // 풀 해제. 동시에 모든 객체도 해제된다.
  [pool release]; 
  
  return 0;
}


* 출력 결과

Fraction 1: 2/5
Fraction 2: 2/5


* 이 예제에서 새로 추가한 메소드는 클래스 레벨 메소드이다. 객체가 생성된 후 autorelease 가 호출되었으며, main 함수 내에서는 이 객체의 release 를 호출하지 않는다. 
* 객체 해제가 동작하는 이유를 살펴보자. 모든 retain 은 하나의 release 또는 autorelease 와 쌍을 이루어야 한다. 이 예제에서 retain 카운트는 1이고 한번의 autorelease 를 호출하였다. 1-1=0 이라는 이야기. 이 Autorelease pool 이 release 되면, autorelease 를 호출한 모든 객체는 그 호출 횟수만큼 [obj release] 가 실행된다. 
* 주석에 설명해두었듯이, 해당라인의 주석을 지우고 실행해보면 segment fault 가 발생한다. autorelease 가 이미 객체에서 호출되었기 때문에 release 를 호출하고 Autorelease pool 에서 다시 release 를 호출하게 되므로 이미 nil 이 된 객체의 dealloc을 호출하는 상황이 되는 것이다. 수식으로 보면 1(생성) - 1(release) -1(autorelease) = -1 이 되는 것이다. 
* Autorelease pool 은 많은 양의 임시객체를 만들어내는 데 사용할 수 있다. 이 때는 먼저 풀을 만든 뒤 많은 임시객체를 생성하고 마지막에 풀을 제거하는 것이다. 당연한 이야기지만 한번에 한개 이상의 Autorelease pool 만드는 것도 얼마든지 가능하다. 



Foundation 프레임워크 클래스 

* Foundation 프레임워크는 C++ 의 Standard Template Library 와 유사하다. Objective-C는 진짜 동적 자료형을 다루기 때문에 C++의 템플릿처럼 복잡하게 코딩할 필요가 없다. 이 프레임워크에는 자료구조, 네트워킹, 쓰레드를 비롯한 여러가지가 포함되어있다. 


NSArray

* ArrayTest 프로젝트를 만들고 다음 내용을 코딩하자. 

* main.m

#import <Foundation/NSArray.h>
#import <Foundation/NSString.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSEnumerator.h>

void print( NSArray *array ) {
  NSEnumerator *enumerator = [array objectEnumerator];
  id obj;
  
  while ( obj = [enumerator nextObject] ) {
    printf( "%s\n", [[obj description] cString] );
  }
  
}

int main ( int argc, const char *argv[] ) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  
  NSArray *arr = [[NSArray alloc] initWithObjects:
                    @"Me", @"Myself", @"I", nil];
  NSMutableArray *marr = [[NSMutableArray alloc] init];
  
  // arr 출력. 
  printf( "---static array\n" );
  print( arr );
  
  // marr 에 아이템 추가
  [marr addObject: @"One"];
  [marr addObject: @"Two"];
  [marr addObjectsFromArray: arr];
  [marr addObject: @"Three"];
  
  // marr 출력
  printf( "---mutable array\n" );
  print( marr );
  
  // 정렬 후 출력. 
  printf( "---sorted mutable array\n" );
  [marr sortUsingSelector: @selector( caseInsensitiveCompare: )];
  print( marr );
  
  
  // 메모리 해제
  [arr release];
  [marr release];
  [pool release];
  
  return 0;
};

 원문에 쓰인 mutable 이 C++ 예약어이므로 marr 로 바꾸어주었다. 수정한 Dev-C++ 이 *.c 와 *.m 을 구분하지 않기 때문에... 


* 출력 결과

---static array
Me
Myself
I
---mutable array
One
Two
Me
Myself
I
Three
---sorted mutable array
I
Me
Myself
One
Three
Two


* Foundation 프레임워크에는 NSArray 와 NSMutableArray 두 종류의 배열이 있다. 이 물건은 Foundation 프레임워크에서 가장 데이터에 중점을 둔 물건들이다. 이름에서 보이듯이, Mutable 은 크기가 변할 수 있지만 NSArray는 그렇지 않다. 다시 말해 NSArray는 한번 만들어진 후에는 그 길이를 바꿀 수 없다. 
* 배열은 생성자에서 Obj, Obj, Obj, ... nil 의 형태로 초기화시킬 수도 있다. 여기서 nil은 끝을 의미한다. 
* 정렬 부분은 셀렉터를 사용해 객체를 정렬하는 방법을 보여준다. 여기서 셀렉터는 array 객체에 정렬할 때 NSString의 대소문자 구분없는 비교함수를 사용하라고 알리고 있다. 
* print 함수에서는 description 메소드를 사용했다. 자바의 toString과 비슷한 이 메소드는 객체를 NSString 으로 표현해 반환한다. 
* NSEnumerator 는 자바의 enumerator 시스템과 유사하다. while ( obj = [array objectEnumerator] )이 작동하는 원리는 obj가 가장 마지막 객체에 들어가면 nil이 반환되기 때문이다. 보통 C에서는 nil은 0이고, 곧 false이다. 보다 정확하게 하려면 ( ( obj = [array objectEnumerator] ) != nil ) 이 나을지도 모르겠다.



NSDictionary

* DicTest 프로젝트를 생성하고 다음 예제를 코딩하자. 

* main.m

#import <Foundation/NSString.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/Foundation.h>
#import <stdio.h>

void print( NSDictionary *map ) {
  NSEnumerator *enumerator = [map keyEnumerator];
  id key;
  
  while ( key = [enumerator nextObject] ) {
    printf( "%s => %s\n", 
      [[key description] cString], 
      [[[map objectForKey: key] description] cString]
    );
  }
  
}

int main ( int argc, const char *argv[] ) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  
  NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:
    @"one", [NSNumber numberWithInt: 1],
    @"two", [NSNumber numberWithInt: 2], 
    @"three", [NSNumber numberWithInt: 3],
    nil];
  NSMutableDictionary *mdic = [[NSMutableDictionary alloc] init];
  
  // dic 출력. 
  printf( "---static dictionary\n" );
  print( dic );
  
  // mdic에 객체 추가. 
  [mdic setObject: @"Tom" forKey: @"tom@jones.com"];
  [mdic setObject: @"Bob" forKey: @"bob@dole.com"];
  
  // mdic 출력. 
  printf( "---mutable dictionary\n" );
  print( mdic );
  
  // 메모리 해제.
  [dic release];
  [mdic release];
  [pool release];
  
  return 0;
  
}


* 출력 결과

---static dictionary
1 => one
3 => three
2 => two
---mutable dictionary
bob@dole.com => Bob
tom@jones.com => Tom



****

가이드를 하나씩 따라가보니 Objective-C 에 대해 가지고 있던 거부감이 확실히 줄어드는 느낌... 
깔끔한 맛이 C++ 보다는 오히려 파스칼에 가깝다. 
Dev-C++ 에서 따라한 모든 예제는 여기. 


Dev-C++ 에서 오브젝티브 C 가지고 놀기.

탐구생활/Objective-C
윈도에 GNUStep 을 설치하고 Dev-C++ 에서 Objective-C 를 학습하는 멋진 강좌가 있다. 


따라해보니 잘된다. 감동이 줄줄. 다만 컴파일 및 링크설정은 다음과 같이 바꿔주자. 

* 컴파일러 추가명령

-fconstant-string-class=NSConstantString


* 링커 추가명령

-lobjc -lgnustep-base -enable-auto-import


아쉬운 것은 Dev-C++ 이 원래 C++ 기반이므로 Objective-C 의 예약어의 문법강조가 제대로 되지 않는다는 점. 
때문에 아래 그림처럼 implementation 나 end 에서 색이 바뀌지 않는다... 




Dev-C++ 은 델파이로 진행중인 오픈소스 프로젝트이다. 고쳐서 함 써보자.  

* 예약어 implementation, end, id, YES, NO 에 대한 하이라이팅 추가. 
* SynEdit 컨트롤에 한글입력 패치 적용. 
* 문법 강조 대상 확장자에 m 과 mm 추가. 

그리하여 나온 결과물은 짜잔~~




위의 강좌들에서 사용한 Dev-C++ v5 (4.9.9.2) 기반으로 작업되었으며 컴파일한 실행파일을 올려둔다. 
압축을 풀고 Dev-CPP 디렉토리에 덮어쓰면 된다. 



수정사항은 대충 다음과 같다. 

* devcfg.pas : fSynExt 기본값에 m과 mm을 추가. (1526 라인)
* main.pas : XP테마 설정/설정해제시 메인메뉴배경이 노랗게 되는 문제 수정. 
* editor.pas : 컴파일 경고 두개 수정. 

-Vcl
  -synedit
    -Source
      * SynHighlighterCpp.pas : id, YES, NO, implementation, end 키워드 추가. 
      * SynEdit.pas, SynEditKeyCmds.pas : 볼포 민병권님의 한글입력 패치 적용. (버전이 달라 일일이 삽질. ㅠㅠ)



생각같아서는 *.m 형식으로 저장도 추가하고 싶지만, 귀차니즘에 못이겨 후일을 기약함. 




오후 5시의 나른함이 귀차니즘을 극복. 

* main.pas : *.m, *.mm 파일의 읽고 쓰기 추가. 
* version.pas, utils.pas : *.m, *.mm 파일에 대한 지원 추가
* icons.rc : m.ico, mm.ico 파일 추가 리소싱. 
* FileAssocs.pas, devcfg.pas : *.m, *.mm 파일에 대한 셸 연결 추가

 










소스 관리하고 백업하기 귀찮아서 아예 네이버 개발자 센터에 자리를 냄. 

최신버전 릴리즈는 이후 저기서 하기로 함.