본문 바로가기

카테고리 없음

Objective C dynamic binding(동적 바인딩)과 id형 예제 따라하기

- 동적 바인딩과 id형

동적 바인딩은 객체에 호출되는 실제 메서드를 알아내는 시기를 프로그램 실행 중으로 미룬다.
id 데이터 형은 일반 객체형이다. 그것은 id형을 써서 어느 클래스의 객체든 저장할 수 있다는 의미다. id 형은 프로그램이 실행되는 동안 다양한 데이터 형 객체를 여기에 저장할 때에야 비로소 진정한 강점을 맛 보게 된다. 

- 소스코드

#import <Foundation/Foundation.h>


@interface Complex : NSObject { 

double real;

double imaginary; 

}


@property double real, imaginary;


-(void) print;

-(void) setReal: (double) a andImaginary:(double) b; 

-(Complex *) add: (Complex *) f;

@end 

#import "Complex.h"


@implementation Complex 

@synthesize real, imaginary; 

-(void) print

{

NSLog (@" %g + %gi "realimaginary); 

}


-(void) setReal: (double) a andImaginary: (double) b {

real = a;

imaginary = b; 

}


-(Complex *) add: (Complex *) f {

Complex *result = [[Complex allocinit];

[result setRealreal + [f realandImaginary

 imaginary + [f imaginary]]; return result;

}

@end 

#import <Foundation/Foundation.h>


@interface Fraction : NSObject

int numerator;

int denominator; 

}


@property int numerator, denominator;


-(void) print;

-(void) setTo: (int) n over: (int) d; 

-(double) convertToNum;

-(void) reduce;

-(Fraction *) add: (Fraction *) f;


@end 

#import "Fraction.h"


@implementation Fraction


@synthesize numerator, denominator;


-(void) print {

NSLog (@"%i/%i"numeratordenominator); }

-(double) convertToNum {

if (denominator != 0)

return (doublenumerator / denominator;

else

return 1.0;

}

-(void) setTo: (int) n over: (int) d {

numerator = n;

denominator = d; 

}

-(void) reduce {

int u = numeratorint v = denominatorint temp;

while (v != 0) { temp = u % v;

u = v;

v = temp; 

}

numerator /= u;

denominator /= u; 

}

-(Fraction *) add: (Fraction *) f {

// To add two fractions:

// a/b + c/d = ((a*d) + (b*c) / (b * d)

// result will store the result of the addition 

Fraction *result = [[Fraction allocinit];

int resultNum, resultDenom;

resultNum = numerator * f.denominator + denominator * f.numerator

resultDenom = denominator * f.denominator;

[result setTo: resultNum over: resultDenom]; 

[result reduce];

return result; 

}

@end 

#import <Foundation/Foundation.h>

#import "Fraction.h" 

#import "Complex.h"


int main(int argc, const char * argv[])

{


Fraction *f1 = [[Fraction allocinit]; 

Fraction *f2 = [[Fraction allocinit]; 

Fraction *fracResult;

Complex *c1 = [[Complex allocinit]; 

Complex *c2 = [[Complex allocinit]; 

Complex *compResult;

[f1 setTo1 over10]; 

[f2 setTo2 over15];

[c1 setReal18.0 andImaginary2.5]; 

[c2 setReal: -5.0 andImaginary3.2];

// add and print 2 complex numbers

[c1 print]; 

NSLog (@" ---------"); 

   

compResult = [c1 add: c2]; 

[compResult print];

NSLog (@"\n");

[c2 print];

// add and print 2 fractions

[f1 print]; 

NSLog (@" +"); 

[f2 print]; 

NSLog (@"----");

fracResult = [f1 add: f2];

[fracResult print];

    return 0;

} 


#import <Foundation/Foundation.h>

#import "Fraction.h" 

#import "Complex.h"


int main(int argc, const char * argv[])

{

//NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init];

id dataValue;

Fraction *f1 = [[Fraction alloc] init]; 

Complex *c1 = [[Complex alloc] init];

[f1 setTo: 2 over: 5];

[c1 setReal: 10.0 andImaginary: 2.5];

// first dataValue gets a fraction

dataValue = f1; [dataValue print];

// now dataValue gets a complex number

dataValue = c1; [dataValue print];

return 0

} 

- 컴파일 시기와 런타임 확인

id 변수에 저장된 객체의 데이터 형을 컴파일할 때는 정확히 알 수 없기 때문에, 몇몇 테스트는 런타임 시 기, 즉 프로그램을 실행할 때까지 미루게 된다.
다음 예제 코드를 보자.

Fraction *f1 = [[Fraction alloc] init];

[f1 setReal: 10.0 andImaginary: 2.5];
setReal:andImaginary:메서드가 분수가 아니라 복소수에 적용되기 때문에 이 코드를 포함하는 프로그램 을 컴파일하면 경고 메시지가 뜰 것이다.
OBjective-C 컴파일러는 f1이 선언됨으로써 f1이 Fraction 객체임을 알게 된다. 또한 다음 메시지 표현식 을 접하면서, Fraction 클래스가 setReal:andImaginary: 메서드를 보유하지 않았다는 사실도(그리고 상속 받지도 않았다는 사실까지) 알게 된다. 따라서, 컴파일러는 경고 메시지를 표시할 것이다.
이제 다음 코드를 살펴보자.

id dataValue = [[Fraction alloc] init];
...
[dataValue setReal: 10.0 andImaginary: 2.5];

이 코드를 컴파일하면 컴파일러가 소스파일을 처리할 때 dataValue에 담긴 객체의 데이터 형을 모르기 때문에, 경고 메시지를 생성하지 않는다.
이 줄이 들어 있는 부분을 실행하기 전까지 오류 메시지는 발생하지 않는다.
먼저 런타임 시스템은 dataValue에 담긴 객체의 형을 검사한다. dataValue가 Fraction 변수를 담고 있 으므로, setReal:andImaginary: 메서드가 Fraction에 정의되어 있는 메서드인지 확인한다. 당연히 정의되 지 않았기 때문에 오류 메시지가 발생하고 프로그램은 종료된다. 


- id 데이터 형과 정적 타이핑

만일 id 데이터 형이 어느 객체든 담을 수 있다면, 왜 모든 객체를 그냥 id 형으로 선언하지 않는 것일까? 이 일반 클래스 데이터 형을 남용하지 않는 데는 몇 가지 이유가 있다.
먼저 정적 타이핑이라는 말은, 특정 클래스의 객체로 변수를 정의하는 것이다. ‘정적’이라는 말은 변수가 언제나 그 특정 클래스를 저장하는 데만 사용된다는 의미다. 따라서 저장된 객체의 클래스는 언제나 이미 정해져 있다. 혹은 ‘정적이다’라고 할 수도 있다.

정적 타이핑을 사용하면, 컴파일러는 객체에 적용되는 메서드가 그 클래스에 의해 정의되거나 상속되었는 지를 확인할 수 있고, 그렇지 않은 경우엔느 경고 메시지를 표시한다. 따라서 Rectangle형 변수 myRect 를 선언하면 컴파일러는 myRect에 대해 호출되는 메서드가 Rectangle 클래스에 정의되겄거나 수퍼클래 스에서 상속받았는지를 확인한다.

그러나 러나임 도중에도 이렇게 확인한다면, 정적 타이핑이 뭔지 신경 쓸 필요가 있을까? 대답은 ‘신경 써 야 한다’이다. 이유는 오류를 컴파일 단계에서 잡아 내는 편이 실행 단계에서 잡는 것보다 낫기 때문이다. 런타임 시까지 미루면 여러분이 아닌 다른 사람이 프로그램을 사용하다 오류를 보게 될지도 모른다. 프로 그램이 판매되었다면 불쌍한 사용자가 프로그램을 실행하던 도중에 특정 객체가 어던 메서드에 응답하지 않는다는 걸 알게 될 수도 있다.

또 정적 타이핑을 사용하는 이유는 프로그램의 가독성을 높여주기 때문이다. id f1;

Fraction *f1;
둘 중 무엇이 더 이해하기 쉬운가? 어느 쪽이 변수 f1를 쓰는 목적을 좀더 분명히 나타내느냐는 말이다. 이 예제에서는 사용하지 않았지만, 변수에 정적 타이핑과 의미를 파악하기 쉬운 이름을 붙이면 프로그램 코드 자체가 문서화되는 머나먼 길에 조금 더 가까워진다. 


- 동적 타이핑과 인수, 반환 형

만일 동적 타이핑을 사용하여 메서드를 호출한다면 다음 규칙을 기억해 두자. 하나 이상의 클래스에 동일 한 이름의 메서드가 존재한다면, 각 메서드는 인수와 반환형을 자신이 선언하고 구현한 대로 사용해서 컴 파일러가 메시지 표현식에 맞는 코드를 작성할 수 있도록 해줘야 한다.
컴파일러는 자신이 발견한 각 클래스의 선언이 일치하는지를 확인한다. 만일 메서드의 인수나 반환형이 선 언과 다르다면 컴파일러는 경고 메시지를 표시한다.

예를 들어 Fraction과 Complex 클래스 모두 add: 메서드를 담고 있다. 그러나 Fraction 클래스의 add: 메서드는 인수와 반환 값으로 Fraction 객체를 사용하고 Complex 클래스는 Complex 객체를 쓴다. frac1과 myFract는 Fraction 객체이고, comp1과 myComplex가 Complex 객체일 때, 다음 명령에는 아 무런 문제가 발생하지 않을 것이다.

result = [myFraction add: frac1];

result = [myComplex add: comp1];
두 경우 모두 메시지의 수신자가 정적으로 지정되었고 컴파일러는 수신자의 클래스에서 정의한 대로 메서 드를 올바르게 사용하는지 확인할 수 있다.
만일 dataValue1과 dataValue2가 id형 변수라면, 다음 명령문은 컴파일러가 코드를 생성하여 add: 메서 드에 인수를 넘기고 반환 값을 받는 과정을 추측하여 수행한다.

result = [dataValue1 add: dataValue2];
런타임 도중에 Objective-C 런타임 시스템은 dataValue1에 실제로 포함된 객체 클래스를 확인하고, 올바 른 클래스에서 적절한 메서드를 선택하여 실행한다. 하지만 일반적인 경우, 컴파일러는 메서드에 인수를 넘기는 코드나 반환 값을 처리하는 코드를 부정확하게 생성하기도 한다.
예컨대 한 멧드는 객체를 인수로 받고 다른 메서드는 부동소수점 값을 인수로 받는 경우나, 한 메서드는 객체를 반환하는데, 다른 메서드는 정수를 반환하는 경우에 이런 문제가 발생할 수도 있다.
두 메서드가 일치하지 않는 부분이 그저 객체의 데이터 형이 다른 것뿐이라고 해보자(예를 들어, Fraction 의 add: 메서드는 Fraction 객체를 인수와 반환 값으로 사용하고, Complex의 add: 메서드는 Complex 객체를 사용한다). 이 경우에도, 컴파일러는 올바른 코드를 생성할 수 있는데, 메서드에 넘겨지는 인수는 결국 객체를 참조하는 메모리 주소(포인터)이기 때문이다.