Objective-C Tutorial

Tristan O’Tierney이 운영하는 Objective-C Tutorial의 한국어 버전을 번역했다. Tristan에게 허가를 구했고, 내가 번역한 파일은 그가 사용할 수 있도록 했다. Objective-C의 문법을 빠르게 그리고 간략하게 잘 설명해서 여러사람에게 도움이 될 것 같아 번역했다. HTML버전은 http://kjune.com/objc/objective-c-kor.html 원본 사이트 : http://www.otierney.net/objective-c.html.ko

개요


    시작하기

    이 튜토리얼 다운로드

    • 이 가이드에 사용된 소스 코드와 Makefile은 objc.tar.gz에서 다운 받을 수 있다. 이 튜토리얼의 예제들은 Steve Kochan씨가 쓴 Programming in Objective-C에서 인용한 것이다. 더 자세한 정보와 예제가 필요하다면, 이 책을 참조하면 도움이 될 것이다. 이 사이트에서 사용된 예제는 그의 허가를 받고 사용한 것이므로, 다른 곳에 무단 복제를 금지한다.

    환경설정

    </li>
    • Linux/FreeBSD: GNUStep를 설치
      • GNUStep 어플리케이션을 빌드하기 위해서는 먼저 /usr/GNUstep/System/Makefiles/GNUstep.sh에 있는 GNUstep.sh 스크립트를 실행한다. 경로는 시스템에 따라 다를 수 있다. /usr, 혹은 /usr/lib, 혹은 /usr/local를 찾아볼 것. 만약 사용하는 셀이 csh/tcsh 기반의 쉘이라면, GNUStep.csh를 대신 실행시키면 된다. 이 스크립트를 쉘 설정파일인 .bashrc 이나 .cshrc에 추가하는 것을 추천한다.
    • Mac OS X: XCode설치
    • Windows NT 5.X: 먼저 cygwin 이나 mingw를 설치한 후에 GNUStep을 설치

    머리말

    </li>
    • 이 튜토리얼은 사용자가 기본적인 C 지식이 있다고 전제하고 설명을 하고 있다. C의 데이터형, 함수, 반환값, 포인터와 기본적인 메모리 관리등이 이에 해당된다. 아직 여기까지 내용을 모른다면, 다음의 책을 참조하길 바란다. The C Programming Language. 이 책은 C언어를 만든 사람들이 만든 바로 그 책이다.
    • 오브젝티브-C는 C의 파생언어여서 C의 많은 기능을 차용받았다. 하지만 C로 부터 오지 않은 다른 부분들도 있다.
    • nil: C/C++ 에서는 NULL이라고 사용하는 데, 오브젝티브-C에서는 nil이라고 한다. 차이점은 nil에게는 메시지를 전달할 수 있다는 점이다. (예를 들면 이런 식으로 [nil message];) 이렇게 해도 괜찮다. 반면 C에서 NULL에게는 이렇게 할 수 없다.
    • BOOL: C에는 공식적인 불린 형이 없다. 사실 오브젝티브-C에서도 공식적인 불린형은 없다. 하지만 불린형은 Foundation classes에 포함이 되어서 (NSObject.h를 임포트 함으로써) 그리고 nil도 역시 이 헤더파일로부터 추가된다. 오브젝티브-C에서의 BOOL은 YES나 NO라는 2개의 모드를 갖는다. (TRUE나 FALSE가 아니다.)
    • #import 대 #include: 위의 hello world 예제에서 눈치챘겠지만, 파일을 포함할 때 #import를 사용한다. #import는 gcc 컴파일러가 지원한다. 다만 #include를 더 선호하기 때문에 덜 사용되게 될 뿐이다. #import는 사용자가 만든 .h파일의 위와 아래에 #ifndef #define #endif 블럭을 붙이는 것과 같은 일을 한다. 이 블럭을 붙이는 건 정말 멍청한 짓이라고 생각하고, 아마 다른 모든 개발자들도 동의할 것이다. 따라서 그냥 #import를 사용하면 된다. 걱정할 필요도 없고, gcc에서 #import를 제거한다고 해도, 다른 오브젝티브-C 개발자들이 그런 일이 생기지 못하게 할 것이다. 공식적으로 애플에서 #import를 자신들의 코드에 사용하니, 만약 #import가 gcc에서 사라진다고 해도, 애플에서는 gcc를 변형한 버전이라도 출시할 것이다.
    • 비록 메시지가 조금 더 특별한 특성을 가지긴 해도, 메소드와 메시지란 단어는 오브젝티브-C에서는 서로 교환 가능한 말이다. 메시지는 동적으로 다른 객체에게 포워딩 될 수 있다. 오브젝티브-C에서 객체의 메시지를 호출한다는 것은 객체가 그 메시지를 구현한 다는 것을 의미하는 것이 아니다. 단지 객체가 메시지가 전달되었을 때 어떻게 반응해야 하는 지를 안다는 뜻이다. 그 방식은 직접 구현을 하거나 혹은 처리방법을 아는 다른 객체에 메시지를 포워딩하는 방식을 사용할 수 있다.

    hello world 만들기

    </li>
    • hello.m
      #import <stdio.h>
      
      int main( int argc, const char *argv[] ) {
      printf( "hello worldn" );
      return 0;
      }
    • 결과
      hello world
    • 오브젝티브-C에서는 #include 대신에 #import를 사용한다.
    • 오브젝티브-C의 기본 확장자는 .m이다.

클래스 만들기

</li>

    인터페이스 @interface

    </li>
    • 예제의 출처: "Programming in 오브젝티브-C," Copyright © 2004 출판사 Sams Publishing. 허가 받고 사용함
    • 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의 줄임말. 이제는 OpenStep으로 이름이 바꿔서 의미가 맞지 않긴 하다.
    • 상속, 다형성, 그리고 다른 객체 지향 프로그래밍의 기능들은 Class: Parent와 같은 형태로 표현하면 된다. 위의 예제에서는 Fraction: NSObject 부분이다.
    • 인스턴스 변수들은 @interface Class: Parent { .... }의 안쪽으로 들어간다.
    • 아직 접근 레벨이 정해져 있지 않다. (protected, public, private). 기본은 protected이다. 접근 셋팅하는 법은 뒤에 더 다루겠다.
    • 멤버 함수 뒤에는 인스턴스 메소드가 나온다. 포멧은 scope (returnType) methodName: (parameter1Type) parameter1Name;과 같다.
      • 스코프(scope)는 매스드가 클래스에서 사용하는지 인스턴스에서 사용하는 지를 판별한다. 인스턴스에서 사용하는 메소드는 -를, 클래스에서 사용하는 메소드는 +로 시작한다.
    • 인터페이스는 @end로 끝난다.

    @implementation

    </li>
    • 예제의 출처: "Programming in 오브젝티브-C," Copyright © 2004 출판사 Sams Publishing. 허가 받고 사용함
    • 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 ClassName 으로 시작하고, @end으로 끝난다.
    • 모든 정의된 메소드들은 인터페이스에서 정의된 것과 유사한 형태로 구현된다.

    모두 합하기

    </li>
    • 예제의 출처: "Programming in 오브젝티브-C," Copyright © 2004 출판사 Sams Publishing. 허가 받고 사용함
    • main.m
      #import <stdio.h>
      #import "Fraction.h"
      
      int main( int argc, const char *argv[] ) {
      // create a new instance
      Fraction *frac = [[Fraction alloc] init];
      
      // set the values
      [frac setNumerator: 1];
      [frac setDenominator: 3];
      
      // print it
      printf( "The fraction is: " );
      [frac print];
      printf( "n" );
      
      // free memory
      [frac release];
      
      return 0;
      }
    • 결과
      The fraction is: 1/3
    • Fraction *frac = [[Fraction alloc] init];
      • 이 한 줄에는 몇 가지 중요한 것들이 있다.
      • 오브젝티브-C에서 메소드는 [object method]와 같은 형태로 호출된다. C++에서는 object->method() 와 같은 형태로 호출되는 것과 유사하다.
      • 오브젝티브-C에는 값 타입이 없다. 다시 말해 C++처럼 Fraction frac; frac.print(); 이런 식으로 코딩하지 않는 다는 뜻이다. 오브젝티브-C에서는 반드시 객체를 포인터를 통해서 다뤄야 한다.
      • 이 한 줄에서 실제로는 두 가지 일이 일어난다: [Fraction alloc]은 Fracion 클래스의 alloc 메소드를 호출한다. 이 것은 메모리를 할당하는 것과 유사하다. 이 단계에서 메모리 할당이 일어난다.
      • [object init]은 객체 안에 있는 여러 변수들의 값을 초기화하는 생성자 호출이다. 이 메소드는 [Fraction alloc]에 의해서 반환된 인스턴스가 호출한다. 보통 이 과정은 다음과 같은 형태로 한 줄로 이뤄진다. Object* var = [[Object alloc]init];
    • [frac setNumerator: 1]은 간단하다. frac객체의 setNumerator 함수를 호출하면서 인자 1을 전달한다.
    • 다른 C의 변종들과 마찬가지로, 메모리를 해제하는 구조가 있다. 여기에서는 release를 통해서 처리되었는데, 이는 NSObject에서 상속 받은 것이다. 이에 대해서는 뒤에서 더 자세히 다루겠다.

세부적인 내용들

</li>

    매개변수를 여러 개 갖는 경우

    </li>
    • 지금까지는 매개변수가 여러 개인 함수는 나오지 않았다. 처음에 보면 방식이 직관적이지 않다는 생각이 드는데, 이 것은 스몰토크(Smalltalk)로 부터 차용해온 것이다.
    • 예제의 출처: "Programming in 오브젝티브-C," Copyright © 2004 출판사 Sams Publishing. 허가 받고 사용함
    • 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[] ) {
      // create a new instance
      Fraction *frac = [[Fraction alloc] init];
      Fraction *frac2 = [[Fraction alloc] init];
      
      // set the values
      [frac setNumerator: 1];
      [frac setDenominator: 3];
      
      // combined set
      [frac2 setNumerator: 1 andDenominator: 5];
      
      // print it
      printf( "The fraction is: " );
      [frac print];
      printf( "n" );
      
      // print it
      printf( "Fraction 2 is: " );
      [frac2 print];
      printf( "n" );
      
      // free memory
      [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]식으로 하면 된다.
    • 레이블은 써도 되고 안써도 된다. 메소드 이름을 method:::라고 해도 된다. 레이블 이름을 쓰지 않으면 이렇게 된다. 그리고 a: 를 인자를 구분하기 위해서 사용한다. 하지만, 이 방식을 추천하진 않는다.

    생성자

    </li>
    • 예제의 출처: "Programming in 오브젝티브-C," Copyright © 2004 출판사 Sams Publishing. 허가 받고 사용함
    • 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[] ) {
      // create a new instance
      Fraction *frac = [[Fraction alloc] init];
      Fraction *frac2 = [[Fraction alloc] init];
      Fraction *frac3 = [[Fraction alloc] initWithNumerator: 3 denominator: 10];
      
      // set the values
      [frac setNumerator: 1];
      [frac setDenominator: 3];
      
      // combined set
      [frac2 setNumerator: 1 andDenominator: 5];
      
      // print it
      printf( "The fraction is: " );
      [frac print];
      printf( "n" );
      
      printf( "Fraction 2 is: " );
      [frac2 print];
      printf( "n" );
      
      printf( "Fraction 3 is: " );
      [frac3 print];
      printf( "n" );
      
      // free memory
      [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가 등장했다.
      • 자바와 마찬가지로 오브젝티브-C에는 오직 한 개의 부모 클래스만을 가진다.
      • 부모의 생성자를 호출하기 위해서는 [super init]을 호출한다. 적절하게 상속을 하기 위해서 반드시 해야 한다.
      • 여기에 self라는 또 다른 키워드가 등장한다. self는 자기 자신에 대한 인스턴스를 가리킨다. self는 자바나 C++에서의 this와 유사하다.
    • if ( self ) 라고 쓰는 것은 if ( self != nil ) 와 동일하다. 부모 클래스의 생성자로부터 성공적으로 새 객체를 생성해서 받아왔다는 것을 보장하기 위해서 사용한 것이다. 오브젝티브-C에서의 nil은 C/C++의 NULL과 같다. NSObject를 포함함으로써 추가되었다.
    • 값들을 초기화 한 후에는 return self;를 통해서 자기 자신의 객체를 반환해야 한다.
    • 초기 생성자는 -(id) init; 이다.
    • 오브젝티브-C에서의 생성자는 단순한 "init" 메소드이다. C++이나 자바에서처럼 특별한 생성자가 있는 것은 아니다.

    접근 권한

    </li>
    • 기본 권한은 @protected이다.
    • 자바에서는 개별 메소드와 변수 앞에 public/private/protect라고 표시를 한다. 오브젝티브-C는 C++과 유사한 방식이다.
    • 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: %in", a->publicVar );
      
      // doesn't compile
      //a->privateVar = 10;
      //printf( "private var: %in", a->privateVar );
      
      [a release];
      return 0;
      }
    • 결과
      public var: 5
    • 위에서 본 바와 같이 C++에서는 private: [list of vars] public: [list of vars]와 같이 표시하는 것을 여기에서는 @private, @protected, 등등으로 표시한다.

    클래스 수준 접근성

    </li>
    • 종종 클래스 수준에서 변수와 함수를 조절할 수 있는 것이 필요할 때가 있다. 예를 들어, 객체가 인스턴스화 된 횟수를 추적하고자 할 때와 같은 경우이다.
    • 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: %in", [ClassA initCount] );
      
      ClassA *c3 = [[ClassA alloc] init];
      
      // print count again
      printf( "ClassA count: %in", [ClassA initCount] );
      
      [c1 release];
      [c2 release];
      [c3 release];
      
      return 0;
      }
    • 결과
      ClassA count: 2
      ClassA count: 3
    • static int count = 0; 이렇게 클래스 변수를 선언한다. 이 방법이 변수를 보관하는 가장 좋은 방법은 아니다. 자바에서 구현하는 것이 조금 더 좋은 해결책으로 생각되지만, 이 방법도 된다.
    • +(int) initCount; 이 함수가 실제로 객수를 반환한다. 눈여겨볼 차이점이 하나가 있는데 형 앞에 -를 쓴 대신 +를 사용했다. +는 클래스 레벨 함수라는 뜻이다.
    • 클래스 레벨의 변수를 접근 하는 방식은 일반 멤버 변수를 접근 하는 방식과 동일하다. ClassA의 생성자에 있는 count++를 보면 된다.
    • +(void) initialize 메소드는 오브젝티브-C가 프로그램을 시작할 때 호출된다. 그리고 이 모든 클래스에서 호출된다. 따라서 이 예제의 count와 같은 클래스 레벨의 변수를 초기화 하기에 좋은 위치이다.

    예외처리

    </li>
    • 참고: 예외 처리는 오직 맥 OS X 10.3에서 지원됩니다.
    • 예제의 출처: "Programming in 오브젝티브-C," Copyright © 2004 출판사 Sams Publishing. 허가 받고 사용함
    • CupWarningException.h
      #import <Foundation/NSException.h>
      
      @interface CupWarningException: NSException
      @end
    • CupWarningException.m
      #import "CupWarningException.h"
      
      @implementation CupWarningException
      @end
    • CupOverflowException.h
      #import <Foundation/NSException.h>
      
      @interface CupOverflowException: NSException
      @end
    • CupOverflowException.m
      #import "CupOverflowException.h"
      
      @implementation CupOverflowException
      @end
    • Cup.h
      #import <Foundation/NSObject.h>
      
      @interface Cup: NSObject {
      int level;
      }
      
      -(int) level;
      -(void) setLevel: (int) l;
      -(void) fill;
      -(void) empty;
      -(void) print;
      @end
    • Cup.m
      #import "Cup.h"
      #import "CupOverflowException.h"
      #import "CupWarningException.h"
      #import <Foundation/NSException.h>
      #import <Foundation/NSString.h>
      
      @implementation Cup
      -(id) init {
      self = [super init];
      
      if ( self ) {
      [self setLevel: 0];
      }
      
      return self;
      }
      
      -(int) level {
      return level;
      }
      
      -(void) setLevel: (int) l {
      level = l;
      
      if ( level > 100 ) {
      // throw overflow
      NSException *e = [CupOverflowException
      exceptionWithName: @"CupOverflowException"
      reason: @"The level is above 100"
      userInfo: nil];
      @throw e;
      } else if ( level >= 50 ) {
      // throw warning
      NSException *e = [CupWarningException
      exceptionWithName: @"CupWarningException"
      reason: @"The level is above or at 50"
      userInfo: nil];
      @throw e;
      } else if ( level < 0 ) {
      // throw exception
      NSException *e = [NSException
      exceptionWithName: @"CupUnderflowException"
      reason: @"The level is below 0"
      userInfo: nil];
      @throw e;
      }
      }
      
      -(void) fill {
      [self setLevel: level + 10];
      }
      
      -(void) empty {
      [self setLevel: level - 10];
      }
      
      -(void) print {
      printf( "Cup level is: %in", level );
      }
      @end
    • main.m
      #import "Cup.h"
      #import "CupOverflowException.h"
      #import "CupWarningException.h"
      #import <Foundation/NSString.h>
      #import <Foundation/NSException.h>
      #import <Foundation/NSAutoreleasePool.h>
      #import <stdio.h>
      
      int main( int argc, const char *argv[] ) {
      NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
      Cup *cup = [[Cup alloc] init];
      int i;
      
      // this will work
      for ( i = 0; i < 4; i++ ) {
      [cup fill];
      [cup print];
      }
      
      // this will throw 예외처리
      for ( i = 0; i < 7; i++ ) {
      @try {
      [cup fill];
      } @catch ( CupWarningException *e ) {
      printf( "%s: ", [[e name] cString] );
      } @catch ( CupOverflowException *e ) {
      printf( "%s: ", [[e name] cString] );
      } @finally {
      [cup print];
      }
      }
      
      // throw a generic exception
      @try {
      [cup setLevel: -1];
      } @catch ( NSException *e ) {
      printf( "%s: %sn", [[e name] cString], [[e reason] cString] );
      }
      
      // free memory 
      [cup release];
      [pool release];
      }
    • 결과
      Cup level is: 10
      Cup level is: 20
      Cup level is: 30
      Cup level is: 40
      CupWarningException: Cup level is: 50
      CupWarningException: Cup level is: 60
      CupWarningException: Cup level is: 70
      CupWarningException: Cup level is: 80
      CupWarningException: Cup level is: 90
      CupWarningException: Cup level is: 100
      CupOverflowException: Cup level is: 110
      CupUnderflowException: The level is below 0
    • NSAutoreleasePool은 메모리 관리 클래스이다. 지금은 일단 넘어가자.
    • 전달 받은 예외를 처리하기 위해서 NSException을 확장할 필요는 없다. 간단히 id를 사용하면 된다. @catch ( id e ) { ... }
    • 마지막 부분에 finally 블럭이 있다. 자바와 유사하게 작동하는데, finally 블럭에 있는 구문은 반드시 호출되게 되어있다.
    • Cub.m에 있는 문자열 @"CupOverflowException"은 NSString 상수 객체이다. 오브젝티브-C에서의 @표시는 언어의 확장을 의미한다. C스트링은 C나 C++과 유사하다. 문자열 상수이고 모두 char*로 만들어져 있다.


상속, 다형성, 그리고 다른 객체 지향 프로그래밍의 기능들

</li>

    id 형

    </li>
    • 오브젝티브-C에는 id라는 특별한 형이 있다. 이것은 여러가지 면에서 void*와 유사하게 사용된다. 하지만 반드시 객체에 대해서만 사용된다. 오브젝티브-C는 객체의 메소드를 호출 하는 방식에 있어서 자바나 C++과 다른데, 이 때에 객체의 타입을 알 필요가 없다. 단지 메소드만 있으면 된다. 이것이 오브젝티브-C에서 메시지를 전달하는 방식으로 간주된다.
    • 예제의 출처: "Programming in 오브젝티브-C," Copyright © 2004 출판사 Sams Publishing. 허가 받고 사용함
    • Fraction.h
      #import <Foundation/NSObject.h>
      
      @interface Fraction: NSObject {
      int numerator;
      int denominator;
      }
      
      -(Fraction*) initWithNumerator: (int) n denominator: (int) d;
      -(void) print;
      -(void) setNumerator: (int) d;
      -(void) setDenominator: (int) d;
      -(void) setNumerator: (int) n andDenominator: (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) setDenominator: (int) d {
      denominator = d;
      }
      
      -(void) setNumerator: (int) n andDenominator: (int) d {
      numerator = n;
      denominator = d;
      }
      
      -(int) denominator {
      return denominator;
      }
      
      -(int) numerator {
      return numerator;
      }
      @end
    • 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[] ) {
      // create a new instance
      Fraction *frac = [[Fraction alloc] initWithNumerator: 1 denominator: 10];
      Complex *comp = [[Complex alloc] initWithReal: 10 andImaginary: 15];
      id number;
      
      // print fraction
      number = frac;
      printf( "The fraction is: " );
      [number print];
      printf( "n" );
      
      // print complex
      number = comp;
      printf( "The complex number is: " );
      [number print];
      printf( "n" );
      
      // free memory
      [frac release];
      [comp release];
      
      return 0;
      }
    • 결과
      The fraction is: 1 / 10
      The complex number is: 10.000000 + 15.000000i
    • 이런 식의 동적인 형 연결을 통해서 여러가지 이득이 있다. 어떤 함수를 호출하기 위해서 그 객체의 형을 반드시 알 필요는 없다. 만약 객체가 메시지에 반응한다면, 그 메소드를 호출할 것이다. 그렇기 때문에 지저분한 캐스팅 문제가 오브젝티브-C에서는 발생하지 않는 것이다. 자바에서는 .intValue() 라는 함수를 호출하려면 먼저 캐스팅을 해야하고, 그 뒤에 메소드를 호출해야 하는 것과는 대조적이다.

    상속, 다형성, 그리고 다른 객체 지향 프로그래밍의 기능들

    </li>
    • 예제의 출처: "Programming in 오브젝티브-C," Copyright © 2004 출판사 Sams Publishing. 허가 받고 사용함
    • Rectangle.h
      #import <Foundation/NSObject.h>
      
      @interface Rectangle: NSObject {
      int width;
      int height;
      }
      
      -(Rectangle*) 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 Rectangle
      -(Rectangle*) 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 Square: Rectangle
      -(Square*) initWithSize: (int) s;
      -(void) setSize: (int) s;
      -(int) size;
      @end
    • Square.m
      #import "Square.h"
      
      @implementation Square
      -(Square*) 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[] ) {
      Rectangle *rec = [[Rectangle alloc] initWithWidth: 10 height: 20];
      Square *sq = [[Square alloc] initWithSize: 15];
      
      // print em
      printf( "Rectangle: " );
      [rec print];
      printf( "n" );
      
      printf( "Square: " );
      [sq print];
      printf( "n" );
      
      // update square
      [sq setWidth: 20];
      printf( "Square after change: " );
      [sq print];
      printf( "n" );
      
      // free memory
      [rec release];
      [sq release];
      
      return 0;
      }
    • 결과
      Rectangle: width = 10, height = 20
      Square: width = 15, height = 15
      Square after change: width = 20, height = 20
    • 오브젝티브-C에서 상속, 다형성, 그리고 다른 객체 지향 프로그래밍의 기능들은 자바와 유사하다. 상위 클래스를 확장하려고 한다면, (단일 상속만 가능하다) 하위 클래스에서 상위 클래스에 있는 메소드와 같은 이름으로 메소드를 선언하고, 해당 내용을 구현을 함으로서 메소드 오버로딩을 할 수 있다. C++처럼 가상 테이블로 장난칠 필요가 없다.
    • 한 가지 짚고 넘어갈 문제가 있다. 만약에 다음과 같이 사각형 객체의 생성자를 출하면 어떻게 될것인가? Square *sq = [[Square alloc] initWithWidth: 10 height: 15]. 답은 이 문장은 컴파일 에러가 날것이다. rectangle 클래스 생성자가 반환하는 값이 Rectangle*이지 Square*가 아니기 때문에 이 문장은 에러가 나는 것이다. 이런 문제가 생길 경우에 id 변수를 사용하는 것이 좋다. 만약 하위 클래스에서 상위 클래스의 생성자를 사용하길 원한다면 Rectangle*의 반환 형을 id로 바꾸면 된다.

    동적 형

    </li>
    • 오브젝티브-C에서는 동적형에서 사용할 수 있는 몇 가지 중요한 메소드가 있다.
      • -(BOOL) isKindOfClass: classObj 이 객체가 ClassObj의 멤버이거나 상속을 받은 하위클래스인지?
        -(BOOL) isMemberOfClass: classObj 이 객체가 classObj의 멤버인지
        -(BOOL) respondsToSelector: selector 이 객체가 selector란 이름의 메소드를 가지고 있는지?
        +(BOOL) instancesRespondToSelector: selector 이 클래스로 만들어진 객체가 지정된 셀렉터에 반응할 능력이 있는지?
        -(id) performSelector: selector 객체의 지정된 셀렉터를 실행한다.
    • NSObject로 부터 상속받은 모든 객체는 클래스 객체를 반환하는 메소드를 가지고 있다. 자바의 getClass() 메소드와 유사한데, 이 클래스 객체는 위의 메소드들을 사용할 수 있다.
    • 오브젝티브-C에서 셀렉터는 메시지를 표현하기 위해서 사용된다. 셀렉터를 만드는 문법은 다음 예를 보면 된다.
    • 예제의 출처: "Programming in 오브젝티브-C," Copyright © 2004 출판사 Sams Publishing. 허가 받고 사용함
    • main.m
      #import "Square.h"
      #import "Rectangle.h"
      #import <stdio.h>
      
      int main( int argc, const char *argv[] ) {
      Rectangle *rec = [[Rectangle alloc] initWithWidth: 10 height: 20];
      Square *sq = [[Square alloc] initWithSize: 15];
      
      // isMemberOfClass
      
      // true 
      if ( [sq isMemberOfClass: [Square class]] == YES ) {
      printf( "square is a member of square classn" );
      }
      
      // false
      if ( [sq isMemberOfClass: [Rectangle class]] == YES ) {
      printf( "square is a member of rectangle classn" );
      }
      
      // false
      if ( [sq isMemberOfClass: [NSObject class]] == YES ) {
      printf( "square is a member of object classn" );
      }
      
      // isKindOfClass
      
      // true 
      if ( [sq isKindOfClass: [Square class]] == YES ) {
      printf( "square is a kind of square classn" );
      }
      
      // true
      if ( [sq isKindOfClass: [Rectangle class]] == YES ) {
      printf( "square is a kind of rectangle classn" );
      }
      
      // true
      if ( [sq isKindOfClass: [NSObject class]] == YES ) {
      printf( "square is a kind of object classn" );
      }
      
      // respondsToSelector
      
      // true
      if ( [sq respondsToSelector: @selector( setSize: )] == YES ) {
      printf( "square responds to setSize: methodn" );
      }
      
      // false
      if ( [sq respondsToSelector: @selector( nonExistant )] == YES ) {
      printf( "square responds to nonExistant methodn" );
      }
      
      // true
      if ( [Square respondsToSelector: @selector( alloc )] == YES ) {
      printf( "square class responds to alloc methodn" );
      }
      
      // instancesRespondToSelector
      
      // false
      if ( [Rectangle instancesRespondToSelector: @selector( setSize: )] == YES ) {
      printf( "rectangle instance responds to setSize: methodn" );
      }
      
      // true
      if ( [Square instancesRespondToSelector: @selector( setSize: )] == YES ) {
      printf( "square instance responds to setSize: methodn" );
      }
      
      // free memory
      [rec release];
      [sq release];
      
      return 0;
      }
    • 결과
      square is a member of square class
      square is a kind of square class
      square is a kind of rectangle class
      square is a kind of object class
      square responds to setSize: method
      square class responds to alloc method
      square instance responds to setSize: method

    카테고리

    </li>
    • 클래스에 메소드를 추가하고 싶을 때에는 보통 클래스를 확장시키면 된다. 이 방법이 여의치 않을 때가 있을 수 있는데 특히 소스코드가 없다던지 할 경우가 있을 것이다. 카테고리는 이미 존재하는 클래스를 확장시키지 않고 원하는 기능을 추가할 수 있도록 해준다. 유사한 기능이 루비(Ruby)에 있다.
    • 예제의 출처: "Programming in 오브젝티브-C," Copyright © 2004 출판사 Sams Publishing. 허가 받고 사용함
    • 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[] ) {
      // create a new instance
      Fraction *frac1 = [[Fraction alloc] initWithNumerator: 1 denominator: 3];
      Fraction *frac2 = [[Fraction alloc] initWithNumerator: 2 denominator: 5];
      Fraction *frac3 = [frac1 mul: frac2];
      
      // print it
      [frac1 print];
      printf( " * " );
      [frac2 print];
      printf( " = " );
      [frac3 print];
      printf( "n" );
      
      // free memory
      [frac1 release];
      [frac2 release];
      [frac3 release];
      
      return 0;
      }
    • 결과
      1/3 * 2/5 = 2/15
    • 마법은 바로 2개의 @implementation과 @interface에 있다. @interface Fraction (Math) 과 @implementation Fraction (Math).
    • 같은 이름으로는 오직 한 개만의 카테고리가 존재한다. 다른 카테고리는 다른 유일한 이름으로 만들어야 한다.
    • 카테고리에는 인스턴스 변수를 추가할 수 없다.
    • 카테고리는 private 메소드를 만들 때 유용한다. 오브젝티브-C에는 자바에서 처럼 private/protected/public 메소드를 구분하는 명확한 개념이 없기 때문에, 감추고 싶은 메소드를 만들 때에는 카테고리를 이용해서 이러한 기능들을 숨길 수 있다. 이 방법은 클래스의 private 메소드들을 클래스 헤더파일(.h)로 빼서 소스파일(*.m)으로 옮기는 것이다. 다음이 방금 이야기 한 것의 간단한 예이다.
    • 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 methodn" );
      }
      @end
      
      // private methods
      @interface MyClass (Private)
      -(void) privateMethod;
      @end
      
      @implementation MyClass (Private)
      -(void) privateMethod {
      printf( "private methodn" );
      }
      @end
    • main.m
      #import "MyClass.h"
      
      int main( int argc, const char *argv[] ) {
      MyClass *obj = [[MyClass alloc] init];
      
      // this compiles
      [obj publicMethod];
      
      // this throws errors when compiling
      //[obj privateMethod];
      
      // free memory
      [obj release];
      
      return 0;
      }
    • 결과
      public method

    포즈

    </li>
    • 포즈는 카테고리와 유사하지만 좀 더 복잡하다. 포즈를 사용하면 클래스를 확장할 수도 있고, 하위 클래스를 포즈함으로써 상위 클래스를 전역적으로 바꿔치기 할 수 있다. 예를 들면, NSArray를 확장한 NSArrayChild 클래스를 만들었다고 하자. 만약 NSArrayChild 클래스를 NSArray를 포즈하도록 했다면, 당신의 코드 전체에서 자동으로 NSArray대신에 NSArrayChild를 사용할 것이다.
    • 예제의 출처: "Programming in 오브젝티브-C," Copyright © 2004 출판사 Sams Publishing. 허가 받고 사용함
    • 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];
      
      // print it
      printf( "The fraction is: " );
      [frac print];
      printf( "n" );
      
      // make FractionB pose as Fraction
      [FractionB poseAsClass: [Fraction class]];
      
      Fraction *frac2 = [[Fraction alloc] initWithNumerator: 3 denominator: 10];
      
      // print it
      printf( "The fraction is: " );
      [frac2 print];
      printf( "n" );
      
      // free memory
      [frac release];
      [frac2 release];
      
      return 0;
      }
    • 결과
      The fraction is: 3/10
      The fraction is: (3/10)
    • 첫번째 fraction에서 출력한 결과는 3/10이지만 두번째 출력은 (3/10)이다. 이 값은 FractionB로 부터 나왔다.
    • 메소드 poseAsClass는 NSObject의 일부분이다. 이 메소드가 하위 클래스를 상위 클래스인 것처럼 행사할 수 있게 해준다.

    프로토콜

    </li>
    • 오브젝티브-C에서의 프로토콜은 자바의 인터페이스나 C++의 순수가상함수와 동일하다.
    • 예제의 출처: "Programming in 오브젝티브-C," Copyright © 2004 출판사 Sams Publishing. 허가 받고 사용함
    • 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) setNumerator: (int) d;
      -(void) setDenominator: (int) d;
      -(void) setNumerator: (int) n andDenominator: (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) setDenominator: (int) d {
      denominator = d;
      }
      
      -(void) setNumerator: (int) n andDenominator: (int) d {
      numerator = n;
      denominator = d;
      }
      
      -(int) denominator {
      return denominator;
      }
      
      -(int) numerator {
      return numerator;
      }
      
      -(Fraction*) copyWithZone: (NSZone*) zone {
      return [[Fraction allocWithZone: zone] initWithNumerator: numerator
      denominator: denominator];
      }
      @end
    • 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;
      @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[] ) {
      // create a new instance
      Fraction *frac = [[Fraction alloc] initWithNumerator: 3 denominator: 10];
      Complex *comp = [[Complex alloc] initWithReal: 5 andImaginary: 15];
      id <Printing> printable;
      id <NSCopying, Printing> copyPrintable;
      
      // print it
      printable = frac;
      printf( "The fraction is: " );
      [printable print];
      printf( "n" );
      
      // print complex
      printable = comp;
      printf( "The complex number is: " );
      [printable print];
      printf( "n" );
      
      // this compiles because Fraction comforms to both Printing and NSCopyable
      copyPrintable = frac;
      
      // this doesn't compile because Complex only conforms to Printing
      //copyPrintable = comp;
      
      // test conformance
      
      // true
      if ( [frac conformsToProtocol: @protocol( NSCopying )] == YES ) {
      printf( "Fraction conforms to NSCopyingn" );
      }
      
      // false
      if ( [comp conformsToProtocol: @protocol( NSCopying )] == YES ) {
      printf( "Complex conforms to NSCopyingn" );
      }
      
      // free memory
      [frac release];
      [comp release];
      
      return 0;
      }
    • 결과
      The fraction is: 3/10
      The complex number is: 5.000000 + 15.000000i
      Fraction conforms to NSCopying
    • 프로토콜 문법은 간단하다. @protocol ProtocolName (methods you must implement) @end 이라고 작성하면 된다
    • 프로토콜에 따르게 하기 위해서는 따르게 하고자 하는 프로토콜을 <> 안에 넣고 쉼표로 이 것들을 구분한다. 예: @interface SomeClass <Protocol1, Protocol2, Protocol3>
    • 프로토콜이 사용하기 위해서 구현되어야 하는 메소드들은 헤더파일에 메소드의 리스트에 있을 필요는 없다. 위에서 보는 바와 마찬가지로 Complex.h에는 -(void) print에 대한 정의가 없다. 하지만 protocol에 부합되기 위해서 구현이 되어야 한다.
    • 오브젝티브-C'의 인터페이스 시스템에서 한가지 특이한 점은 사용자가 어떻게 형을 지정하는가 하는 점이다. 자바나 C++에서라면 다음과 같이 Printing *someVar = ( Printing * ) frac; 명시적으로 형을 표현했을 것이지만, 오브젝티브-C에서는 id형과 프로토콜을 사용해서 다음과 같이 id var = frac; 표현한다. 이것이 여러 개의 프로토콜에서 필요로 하는 형을 단 하나의 변수를 사용해서 동적으로 명시하도록 해준다. 다음의 예를 보라 id <Printing, NSCopying> var = frac; </li>
    • 객체의 상속을 시험하기 위해 @selector를 사용하는 것과 마찬가지로, 인터페이스의 합치성을 시험하기 위해서 @protocol을 사용할 수 있다. [object conformsToProtocol: @protocol( SomeProtocol )]은 프로토콜과 객체가 규격을 준수하는지의 여부를 BOOL형으로 반환하는 함수이다. 클래스의 경우에도 마찬가지로 사용할 수 있다. [SomeClass conformsToProtocol: @protocol( SomeProtocol )].
    • </ul> </ul>

      메모리 관리

      </li>
      • 지금까지 오브젝티브-C에서 메모리 관리하는 부분에 대해 자세히 설명을 하지 않았었다. 물론 객체는 dealloc하면 메모리가 해지되겠지만, 객체가 다른 객체로의 포인터를 가지고 있는 경우에는 어떻게 할 것인가? 이런 객체의 메모리를 해제할 때에는 주의를 기울여야 할 것이다. 도한 파운데이션 프레임워크에서는 클래스를 만들 때 어떻게 메모리를 관리하는 지에 대해서도 알아야 할 것이다. 이런 것에 대한 설명을 이제 시작하려고 한다.
      • 주의: 지금까지 작성한 예제들은 모두 적절히 메모리가 관리되었다. 혹시 궁금해할까바.
      • Retain과 Release

        </li>
        • Retain과 release는 NSObject를 상속받은 모든 객체가 가지고 있는 2개의 메소드이다. 모든 객체는 내부적으로 카운터를 가지고 있어서 객체의 레퍼런스의 개수를 추적하고 있다. 그래서 만약 3개의 레퍼런스를 가지고 있는 경우에는 자기 자신을 dealloc하면 안될 것이다. 만약 0개의 레퍼런스를 가지고 있다면 dealloc해도 될 것이다. [object retain]은 레퍼런스 카운터를 하나 증가시키고 [object release]는 하나 감소시킨다. 만약 [object release]가 호출되어서 카운터가 0에 도달하면 dealloc이 호출된다.
        • Fraction.m
          ...
          -(void) dealloc {
          printf( "Deallocing fractionn" );
          [super dealloc];
          }
          ...
        • 예제의 출처: "Programming in 오브젝티브-C," Copyright © 2004 출판사 Sams Publishing. 허가 받고 사용함
        • 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];
          
          // print current counts
          printf( "Fraction 1 retain count: %in", [frac1 retainCount] );
          printf( "Fraction 2 retain count: %in", [frac2 retainCount] );
          
          // increment them
          [frac1 retain]; // 2
          [frac1 retain]; // 3
          [frac2 retain]; // 2
          
          // print current counts
          printf( "Fraction 1 retain count: %in", [frac1 retainCount] );
          printf( "Fraction 2 retain count: %in", [frac2 retainCount] );
          
          // decrement
          [frac1 release]; // 2
          [frac2 release]; // 1
          
          // print current counts
          printf( "Fraction 1 retain count: %in", [frac1 retainCount] );
          printf( "Fraction 2 retain count: %in", [frac2 retainCount] );
          
          // release them until they dealloc themselves
          [frac1 release]; // 1
          [frac1 release]; // 0
          [frac2 release]; // 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

        </li>
        • 객체가 다른 객체를 포함하고 있는 경우에는 자기 자신을 제거하기 전에 다른 객체도 메모리에서 제거시켜야 한다. 오브젝티브-C를 사용할 때의 다른 좋은 점은 nil에도 메시지를 전달할 수 있기 때문에 객체를 제거할 때 에러 검사를 많이 할 필요가 없다.
        • 예제의 출처: "Programming in 오브젝티브-C," Copyright © 2004 출판사 Sams Publishing. 허가 받고 사용함
        • 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[] ) {
          NSString *first =[[NSString alloc] initWithCString: "Tom"];
          NSString *last = [[NSString alloc] initWithCString: "Jones"];
          NSString *email = [[NSString alloc] initWithCString: "[email protected]"];
          AddressCard *tom = [[AddressCard alloc] initWithFirst: first
          last: last
          email: email];
          
          // we're done with the strings, so we must dealloc them
          [first release];
          [last release];
          [email release];
          
          // print to show the retain count
          printf( "Retain count: %in", [[tom first] retainCount] );
          [tom print];
          printf( "n" );
          
          // free memory
          [tom release];
          
          return 0;
          }
        • 결과
          Retain count: 1
          Tom Jones <[email protected]>
        • 이 예제의 AddressCard.m을 보면 어떻게 dealloc 메소드를 호출하는 지와 AddressCard 클래스의 멤버 변수들의 메모리를 해제할 때 어떻게 해야 하는 지를 알 수 있다.
        • 각각의 set 메소드에서 일어나는 3동작의 순서가 매우 중요하다. set 함수에 자신의 인자를 넘긴다고 해보자(좀 이상한 예이지만 이런 경우도 있다.) 만약 release를 먼저하고 그 다음에 retain을 한다면 자기 자신을 제거해 버릴 것이다. 따라서 항상 반드시 다음의 순서로 값을 설정하는 것이 중요하다. 1) retain 2) release 3) 값 정하기
        • 보통의 경우에 C 문자열로 변수를 초기하는 경우는 없을 것이다. 왜냐면 C 스트링은 유니코드를 지원하지 않기 때문이다. 다음의 예제에서는 NSAutoreleasePool을 사용하는데, 여기에서 문자열을 사용하고 초기화하는 적절한 방법을 다룰 것이다.
        • 이 방법은 멤버 변수 메모리 관리하는 한 방법에 불과하다. 또 다른 방법으로는 set 메소드 안에서 값을 복사하는 방법이 있다.

        Autorelease Pool

        • NSString과 다른 파운데이션 프레임워크를 이용해서 프로그래밍 할 때 더 유연한 시스템이 필요할 것이다. 이 시스템은 Autolease Pool을 사용한다.
        • 맥 코코아 어플리케이션을 사용할 때, auto release pool은 자동으로 사용 가능하다.
        • 예제의 출처: "Programming in 오브젝티브-C," Copyright © 2004 출판사 Sams Publishing. 허가 받고 사용함
        • 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 the pool"];
          NSString *str3 = [[NSString alloc] initWithString: @"self managed string"];
          
          // print the strings
          printf( "%s retain count: %xn", [str1 cString], [str1 retainCount] );
          printf( "%s retain count: %xn", [str2 cString], [str2 retainCount] );
          printf( "%s retain count: %xn", [str3 cString], [str3 retainCount] );
          
          // free memory
          [str3 release];
          
          // free pool
          [pool release];
          return 0;
          }
        • 결과
          constant string retain count: ffffffff
          string managed by the pool retain count: 1
          self managed string retain count: 1
        • 이 예제에서 몇 가지가 눈에 뛸 것이다. 먼저 str1의 카운터가 ffffff이다.
        • 다른 점은 str3만 release 했는데, 이 프로그램에서 메모리 관리는 완벽히 이뤄지고 있다. 그 이유는 첫번째 상수 문자열은 자동으로 autorelease pool에 추가되었고, 다른 문자열은 stringWithString으로 만들어 졌는데, 이 함수는 NSString 클래스에 포함된 문자열을 만들면서 자체적으로 auto release pool에 문자열을 추가한다.
        • 적절한 메모리 관리를 위해서 반드시 기억해야 할 점은, 편의성을 위해 만든 [NSString stringWithString: @"String"]와 같은 함수들은 autorelease pool을 사용한다는 점이다. 반면 [[NSString alloc] initWithString: @"String"]과 같이 직접 메모리를 할당해서 만든 함수들은 메모리 관리를 위해 autorelease pools을 사용하지 않는다는 점이다.
        • 오브젝티브-C에서는 메모리를 관리하는데 2가지 방법이 있다. 1) retain과 release 혹은 2) retain과 release/autorelease.
        • 모든 retain에는 반드시 한 개의 release나 autorealse가 있어야 한다.
        • 다음 예제가 이 내용을 설명하고 있다.
        • 예제의 출처: "Programming in 오브젝티브-C," Copyright © 2004 출판사 Sams Publishing. 허가 받고 사용함
        • 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];
          
          // print frac 1
          printf( "Fraction 1: " );
          [frac1 print];
          printf( "n" );
          
          // print frac 2
          printf( "Fraction 2: " );
          [frac2 print];
          printf( "n" );
          
          // this causes a segmentation fault
          //[frac1 release];
          
          // release the pool and all objects in it
          [pool release];
          return 0;
          }
        • 결과
          Fraction 1: 2/5
          Fraction 2: 1/3
        • 이 예제에서, 모든 메소드는 클래스 레벨 메소드 이다. 객체가 생성된 후에, autorelease가 호출되었다. main 블럭 안에서 객체에 대한 release는 부르지 않았다.
        • 이 예제가 작동하는 원리는 다음과 같다. 매번 retain이 일어났을 경우에는 반드시 release나 autorelease가 불려져야 한다. 객체의 카운터는 1부터 시작한다. 그리고 autorelease를 부르면 1 - 1 = 0 즉 메모리가 해제되게 된다. 따라서 autorelease pool이 해제되면, 모든 객체의 autorelease 호출된 횟수를 세서 [obj release]를 이용해서 객체당 autorelease가 불려진 횟수와 같은 숫자만큼 카운트를 줄여나간다.
        • 위의 주석에 있는 것처럼, 주석 처리된 줄을 주석 해제 시키면 세그먼트 오류가 날 것이다. autorelease가 이미 객체에서 불려졌기 때문에, release를 호출하고, autorelease pool에서 다시 release를 호출하면 이미 nil이 된 객체에서 다시 dealloc을 호출하는 것이 될 테니 올바르지 않다. 수식으로 다시 정리해보면 1(생성) - 1(release) - 1(autorelease) = -1이 되는 것이다.
        • Auto release pools은 동적으로 많은 양의 임시 객체들을 만들어 낼 수 있다. 이 때의 올바른 절차는 먼저 풀을 만들고, 많은 임시 객체를 만드는 코드를 실행을 시킨 다음에 풀을 제거하는 것이다. 혹시 궁금할 경우에 대비해서 이야기 하면, 한번에 1개 이상의 auto release pool을 갖는 것도 가능하다.

      Foundation framework 클래스

      </li>
      • Foundation framework은 C++의 Standard Template Library와 유사하다. 오브젝티브-C에는 진짜 동적 형이 있어서, C++의 탬플릿처럼 조잡하게 코딩하지 않아도 된다. 이 프레임워크에는 자료 구조, 네트워킹, 쓰레드 외 여러가지 것들이 포함되어 있다.
      • NSArray

        </li>
        • 예제의 출처: "Programming in 오브젝티브-C," Copyright © 2004 출판사 Sams Publishing. 허가 받고 사용함
        • main.m
          #import <Foundation/NSArray.h>
          #import <Foundation/NSString.h>
          #import <Foundation/NSAutoreleasePool.h>
          #import <Foundation/NSEnumerator.h>
          #import <stdio.h>
          
          void print( NSArray *array ) {
          NSEnumerator *enumerator = [array objectEnumerator];
          id obj;
          
          while ( obj = [enumerator nextObject] ) {
          printf( "%sn", [[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 *mutable = [[NSMutableArray alloc] init];
          
          // enumerate over items
          printf( "----static arrayn" );
          print( arr );
          
          // add stuff
          [mutable addObject: @"One"];
          [mutable addObject: @"Two"];
          [mutable addObjectsFromArray: arr];
          [mutable addObject: @"Three"];
          
          // print em
          printf( "----mutable arrayn" );
          print( mutable );
          
          // sort then print
          printf( "----sorted mutable arrayn" );
          [mutable sortUsingSelector: @selector( caseInsensitiveCompare: )];
          print( mutable );
          
          // free memory
          [arr release];
          [mutable release];
          [pool release];
          
          return 0;
          }
        • 결과
          ----static array
          Me
          Myself
          I
          ----mutable array
          One
          Two
          Me
          Myself
          I
          Three
          ----sorted mutable array
          I
          Me
          Myself
          One
          Three
          Two
        • NSArray와 NSMutableArray의 두 가지 종류의 배열이 있다. (파운데이션 클래스에서 가장 데이터에 중점을 둔). 이름에서도 알 수 있듯이 Mutable은 교환 가능하고 NSArray는 그렇지 않다. 다시 말해 NSArray를 만들 수는 있지만, 한번 만든 후에는 길이를 변화시킬 수 없다는 뜻이다.
        • 배열은 생성자에 Obj, Obj, Obj, ..., nil 이런 형태로 배열을 초기화 시킬 수도 있다. 여기에서 nil은 끝을 표시해준다.
        • 정렬 부분은 셀렉터(selector)를 사용해서 객체를 정렬하는 법을 보여준다. 셀러터는 배열을 NSString의 대소문자를 구분하지 않는 비교를 사용하라고 지시해 주고 있다. 만약 객체가 여러가지 정렬 방법을 있다면, 사용자가 셀렉터를 이용해서 자신이 원하는 정렬방법을 지정해 줘야 한다.
        • print 메소드에서, 메소드 표시를 사용했다. 이것은 자바의 toString이랑 유사한다. 객체를 NSString으로 표시한 것을 반환한다.
        • NSEnumerator은 자바의 열거형 시스템이랑 비슷하다. while ( obj = [array objectEnumerator] )이 작동하는 원리는 obj가 가장 마지막 객체에 들어가면 nil이 반환되기 때문이다. 보통 C에서는 nil은 0이고, 곧 false이다. 보다 정확하게 하려면 ( ( obj = [array objectEnumerator] ) != nil ) 이 나을지도 모르겠다.

        NSDictionary

        </li>
        • 예제의 출처: "Programming in 오브젝티브-C," Copyright © 2004 출판사 Sams Publishing. 허가 받고 사용함
        • 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 => %sn",
          [[key description] cString],
          [[[map objectForKey: key] description] cString] );
          }
          }
          
          int main( int argc, const char *argv[] ) {
          NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
          NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:
          @"one", [NSNumber numberWithInt: 1],
          @"two", [NSNumber numberWithInt: 2],
          @"three", [NSNumber numberWithInt: 3],
          nil];
          NSMutableDictionary *mutable = [[NSMutableDictionary alloc] init];
          
          // print dictionary
          printf( "----static dictionaryn" );
          print( dictionary );
          
          // add objects
          [mutable setObject: @"Tom" forKey: @"[email protected]"];
          [mutable setObject: @"Bob" forKey: @"[email protected]" ];
          
          // print mutable dictionary
          printf( "----mutable dictionaryn" );
          print( mutable );
          
          // free memory 
          [dictionary release];
          [mutable release];
          [pool release];
          
          return 0;
          }
        • 결과
          ----static dictionary
          1 => one
          2 => two
          3 => three
          ----mutable dictionary
          [email protected] => Bob
          [email protected] => Tom

      장단점

      </li>

        장점

        </li>
        • 카테고리
        • 포즈
        • 동적형
        • 포인터 카운팅
        • 유연한 메시지 전달
        • C로부터 적당한 정도로 확장을 해서 복잡하지 않음
        • 오브젝티브-C++을 통해서 C++을 사용할 수 있음

        단점

        </li>
        • 네임스페이스가 없음
        • 연산자 오버로딩이 없음(이 점은 장점이라고 할 수 도 있지만, 연산자 오버로딩을 쓰면, 코드의 부자연스러운 점을 줄일 수 있다.)
        • 언어 자체적으로 세련되지 못한 점이 있으나, C++이랑 비슷한 수준

      추가 정보

      </li> </ul>
      최종 수정: April 13, 2004.
      한글 번역: September 11, 2010, by Michael Kang </div>
COMMENTS