오브젝트의 초기화

인스턴스 변수를 보유하고 있는 클래스의 경우, 그 대부분이 초기치를 필요로 합니다. 초기화를 잊어 버렸을 경우, 적절히 동작하지 않을 가능성이 있습니다.

일반적인 객체 지향형 언어에서는, 오브젝트를 안전하게, 확실하게 초기화하기 위한 방법으로서 constructor를 준비하고 있습니다. constructor는 클래스의 인스턴스가 작성된 직후 자동적으로 불려지는 특별한 메소드입니다만, Objective-C는 constructor가 존재하지 않습니다.

그 대신  constructor는 루트 클래스가 사양에 근거해 제공하는 구조를 채용하고 있기 때문에, 루트 클래스의 초기화용 메소드를 명시적으로 호출하지 않으면 안됩니다. Object 루트 클래스나, Mac OS X 의 NSObject 클래스에서는 init 라는 이름의 메소드가 오브젝트의 초기화용 메소드로서 정의되고 있습니다. 이 init 메소드를 initializer라고 부릅니다.

- (id)init;

통상, 오브젝트를 인스턴스화했을 경우는 직후에 init 메세지를 송신하지 않으면 안됩니다. 이 작업은 자동적인 것이 아니고, 명시적으로 메세지로부터 호출하지 않으면 안됩니다. 일반적으로는 다음과 같이 기술되고 있습니다.

[[클래스명 alloc] init]

그러나 new 메소드를 사용하면 상기의 문장을 내부에서 실행해 주기 위해, initializer가 자동적으로 불려 가는 구조를 재현할 수 있습니다. C++ 계의 혈통의 객체 지향형 언어의 경험자이면, 이 쪽이 친숙해 질지도 모릅니다.

+ (id)new;

어느 쪽을 사용할까는 개발자의 취향이지요. 가령, initializer가 특히 초기화해야 할 내용이나 처리 코드가 없는 클래스에 대해서도, 장래의 확장 등에 대응하기 위해서 통일되게 취급하도록하는 방법을 해야 합니다.

Object 루트 클래스를 계승한 새로운 클래스를 선언했을 경우, init 메소드를 오버라이드(override) 하는 것으로 초기화 처리를 추가할 수 있습니다. 인스턴스 변수등의 초기치나, 시스템에 대한 등록의 문의 처리, 클래스가 동작하기 위해서 필요한 사전 데이터의 읽기 등은, 이곳에서 실시하면 좋을 것입니다. 다만, init 메소드를 오버라이드(override) 하는 경우는 Object 클래스의 init 메소드를 호출하는 것을 잊지 말아 주세요.

#import <stdio.h>
#import <objc/Object.h>

@interface Point : Object
{
	int x, y;
}
- (id)init;
- (int)getX;
- (int)getY;
@end

@implementation Point
- (id)init {
	[super init];

	x = y = 0;
	printf("init method\n");
	return self;
}
- (int)getX { return x; }
- (int)getY { return y; }
@end

int main() {
	id pt = [Point new];
	printf("X=%d, Y=%d\n" , [pt getX] , [pt getY]);

	return 0;
}

이 프로그램은, 좌표를 나타내는 X 과 Y 의 값을 제공하는 Point 클래스를 실현하고 있습니다. Point 클래스는 Object 루트 클래스를 계승해, init 메소드를 오버라이드(override) 하고 있습니다. 그 때문에, new 메소드를 이용해 인스턴스를 생성했을 경우, 인스턴스 생성 후에 init 메소드가 불려집니다.

이 프로그램의 init 메소드에서는,  최초로 부모클래스를 적절히 초기화할 필요가 있으므로 super 에 대해서 init 메세지를 송신하고 있습니다. 부모클래스의 init 메소드가 실행되면, 이 시점에서 부모클래스의 모든 기능이 안전하게 이용할 수 있는 것이 보장됩니다. 다음에, 인스턴스 변수 x 와 y 를 0 으로 초기화하고, initializer가 불려지고 있는 것을 시각적으로 확인하기 위해서, printf() 함수로 표준 출력에 문자열을 출력하고 있습니다.


지정 initializer

init 메소드는, 인스턴스가 생성된 직후에 초기화를 위해서 불려지는 통일된 초기화용 메소드입니다만, 인수를 받을 수 없습니다. 서브 클래스가, 서브 클래스에서 새롭게 추가된 변수를 초기화하기 위한 치를 인수로부터 취득할 수 있으면 편리합니다. 그러기 위해서는, 서브 클래스에서 새로운 initializer를 선언하지 않으면 안됩니다. 이러한, Object 루트 클래스의 init 메소드와는 다른 initializer를 지정 initializer라고 부릅니다.

지정 initializer는, 습관적으로 initWith로부터 개시하는 인수를 받는 메소드가 됩니다. 서브 클래스에서 슈퍼 클래스의 지정 initializer를 오버라이드(override) 했을 경우, 반드시 슈퍼 클래스의 지정 initializer를 호출할 필요가 있습니다. 그 이외는, 습관적인 설계의 문제가 되겠지요.

#import <stdio.h>
#import <objc/Object.h>

@interface Point : Object
{
	int x, y;
}
- (id)init;
- (id)initWithPoint:(int)x int:(int)y;
- (int)getX;
- (int)getY;
@end

@implementation Point
- (id)init {
	[super init];
	return [self initWithPoint:0 int:0];
}
- (id)initWithPoint:(int)x int:(int)y {
	self->x = x;
	self->y = y;
	return self;
}

- (int)getX { return x; }
- (int)getY { return y; }
@end

int main() {
	id pt1 = [Point new];
	id pt2 = [[Point alloc] initWithPoint:400 int:300];

	printf("pt1.X=%d, pt1.Y=%d\n" , [pt1 getX] , [pt1 getY]);
	printf("pt2.X=%d, pt2.Y=%d\n" , [pt2 getX] , [pt2 getY]);

	return 0;
}

이 프로그램의 Point 클래스에서는, 지정 initializer initWithPoint 메소드를 정의하고 있습니다. initWithPoint는 자동적으로 불려 지는 메소드는 아니기 때문에, 명시적으로 호출하지 않으면 안됩니다. 통상, init 메소드나 지정 initializer는, 가장 인수를 많이 받는 initializer를 순서에 호출하는 형태가 가장 안정된 설계가 되겠지요.

Posted by tklee

댓글을 달아 주세요