메소드를 클래스간에서 공유한다

프로토콜이라고 하는 용어는, 비교적 네트워크용어로서 이용되고 있습니다만, Objective-C 에 한해서는, 복수의 클래스에서 실장되는 동일한 이름의 메소드를 공유하기 위한 메소드의 선언을 가리킵니다. Java 언어나 C# 언어에서는 인터페이스로 불리는 개념에 가까운 존재입니다.

프로토콜이 이용되는 것은, 프로그램의 설계상의 문제로, 클래스가 특정의 메소드를 구현하고 있는 것을 보장하기 위한 수단으로서 사용됩니다. 예를 들면, 특정의 라이브러리 사양에 근거한 클래스는 지정한 프로토콜에 준거시키지 않으면 안 된다는관계를 쌓아 올릴 수 있습니다. 프로토콜이 제공하는 것은 선언된 메소드만으로, 메소드의 구현이 어떠한 처리를 실행할까는 자유입니다.

프로토콜을 선언하려면 @protocol 컴파일러 지시문을 이용합니다. 이 안에서, 프로토콜이 규약으로서 지정하는 메소드를 선언합니다.

@protocol 프로토콜명 <친프로토콜1 , ...>
프로토콜 본체
...
@end

프로토콜은, 클래스의 계승 관계와 같이 부모프로토콜을 지정해 계승할 수 있습니다. 프로토콜의 계승에 대해서는 잠시 후에 자세하게 해설합니다만, 프로토콜의 계승은 단순하게 부모프로토콜이 정하는 메소드에, 새로운 메소드를 추가하는 의미 밖에 가지지 않습니다. 프로토콜명은, 클래스명등과 같이 C 언어의 명명 규칙에 근거해 프로토콜을 식별하기 위한 이름을 지정합니다. 프로토콜 본체에서는, 메소드의 선언만을 실시할 수 있습니다.

선언한 프로토콜은, 클래스에 채용할 수 있습니다. 프로토콜이 지정되어 있는 클래스는, 그 프로토콜을 채용하고 있다고 부를 수 있습니다. 프로토콜을 채용하고 있는 클래스는, 그 프로토콜로 선언되고 있는 메소드를 반드시 구현하지 않으면 안됩니다. 덧붙여서,  프로토콜로 선언되고 있는 메소드를, 프로토콜을 채용하는 클래스의 선언부에서 재차 선언할 필요는 없습니다. 프로토콜을 클래스에 채용시키려면 , 다음과 같이 클래스를 선언합니다.

@interface 클래스명 : 슈퍼 클래스명 <프로토콜1, ...>

프로토콜은 슈퍼 클래스의 지정의 직후에 < > 그리고 프로토콜명을 둘러싸 지정합니다. 슈퍼 클래스는 1 개 밖에 지정할 수 없습니다만, 프로토콜은 콤마 , 그리고 단락지어 복수의 프로토콜을 채용시킬 수 있습니다. 프로토콜을 채용하는 경우, 반드시 프로토콜로 선언되고 있는 메소드를 @implementation 부에서 구현하지 않으면 안됩니다. 즉,  채용하는 것은 그 클래스가 프로토콜로 선언되고 있는 메소드의 구현을 보장하는 것입니다.

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

@protocol ClassNameToString
- (id) ToString;
@end

@interface A : Object 
{
	char *name;
}
- (id) init;
- (id) free;
@end

@interface B : Object 
@end

@implementation A
- (id) init {
	[super init];
	name = (char *)malloc(255);
	sprintf(name , "%s . A@%d" , __FILE__ , self);
	return self;
}
- (id) free {
	free(name);
	return [super free];
}
- (id) ToString { return (id) name; }
@end
@implementation B
- (id) ToString { return (id)"This is Object of B Class"; }
@end

int main() {
	id objA = [A new];
	id objB = [B new];
	printf("objA = %s\n" , [objA ToString]);
	printf("objB = %s\n" , [objB ToString]);
	[objA free];
	[objB free];

	return 0;
}

이 프로그램은, 클래스의 이름을 나타내는 문자열을 돌려준다 ToString 메소드를 선언한다 ClassNameToString 프로토콜을 선언하고 있습니다. 이 프로토콜을 채용한다 A 클래스와 B 클래스는, 반드시 ToString 메소드를 실장하지 않으면 안됩니다. A 클래스와 B 클래스의 계승 관계에서는 무슨 연결도 없습니다만, 프로토콜을 채용하는 것으로, 이러한 클래스에 ToString 메소드가 실장되고 있는 것을 확실히 보장할 수 있습니다.

실천에서는, 클래스의 실장에 의존하지 않는 완전하게 추상화 된 형태로서 프로토콜이 이용되는 것 외에, 포인터를 사용하지 않고 메소드를 콜백 시키는 방법이라고 해도 이용됩니다. 특히 GUI 환경의 이벤트 처리등에는, 이 프로토콜이 사용되겠지요.

프로토콜은 형태로서 독립하고 있었습니다만, 형태 선언으로, 형명의 직후에 < > 그리고 프로토콜을 지정하는 것으로, 변수가 지정한 프로토콜을 채용하고 있는 것을 명시적으로 선언할 수 있습니다.

형명 <프로토콜명> 변수명 ...

이것은, 함수나 메소드의 인수 선언에서도와 같이 지정할 수 있습니다.

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

@protocol InstanceListener
- (void) InstanceFree:(id)object;
@end

@interface Test : Object
{
	id <InstanceListener> listener;
}
- (id) init;
- (id) free;
- (void)SetInstanceListener:(id <InstanceListener>)l;
- (id <InstanceListener>)GetInstanceListener;
@end

@implementation Test
- (id) init {
	[super init];
	listener = NULL;
	return self;
}
- (id) free {
	if (listener) [listener InstanceFree:self];
	return [super free];
}
- (void)SetInstanceListener:(id <InstanceListener>)l {
	listener = l;
}
- (id <InstanceListener>)GetInstanceListener {
	return listener;
}
@end

@interface WriteInstanceFree : Object <InstanceListener>
@end

@implementation WriteInstanceFree
- (void) InstanceFree:(id)object {
	printf("%X:인스턴스가 해제되었습니다\n" , object);
}
@end

int main() {
	id obj1 = [Test new] , obj2 = [Test new];
	id <InstanceListener> listener = [WriteInstanceFree new];
	[obj1 SetInstanceListener:listener];
	[obj2 SetInstanceListener:listener];
	[obj1 free];
	[obj2 free];

	return 0;
}

이 프로그램에서는, 인스턴스가 해방된 타이밍에 불려 가는 콜백 전문의 InstanceFree 메소드를 선언한다 InstanceListener 프로토콜을 선언하고 있습니다. Test 클래스에서는, 이 프로토콜을 채용하는 인스턴스를 SetInstanceListener 메소드로부터 설정할 수 있어Test 클래스의 인스턴스가 해방되기 직전에 프로토콜의 메소드를 호출합니다. GUI 의 이벤트 처리는 이것과 같은 원리로 처리되겠지요.

변수를 선언할 때, 형명의 직후에 프로토콜을 지정하면, 그 오브젝트가 지정한 프로토콜을 채용하지 않으면 안 된다고 하는 것을 명시합니다. 그 때문에,Test 클래스의 listener 인스턴스 변수는, 확실히 목적의 메소드를 콜백 할 수 있습니다.


프로토콜의 계승

프로토콜은 클래스와 같이 계승할 수 있습니다. 프로토콜을 계승은, 클래스의 계승과는 성질이 달라, 실제로는 기본이 되는 프로토콜에 새로운 메소드 선언을 추가할 뿐입니다. 또, 슈퍼 클래스는 반드시 1 개 밖에 지정할 수 없었습니다만, 프로토콜은 복수의 프로토콜을 계승시킬 수 있습니다.

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

@protocol ProtocolA
- (void) MethodA;
@end

@protocol ProtocolB
- (void) MethodB;
@end

@protocol ProtocolC <ProtocolA>
- (void) MethodC;
@end

@interface Test : Object <ProtocolB, ProtocolC>
@end

@implementation Test
- (void) MethodA { printf("This is MethodA\n"); }
- (void) MethodB { printf("This is MethodB\n"); }
- (void) MethodC { printf("This is MethodC\n"); }
@end

int main() {
	id obj = [Test new];
	[obj MethodA];
	[obj MethodB];
	[obj MethodC];
	[obj free];
	return 0;
}

이 프로그램의 ProtocolC 프로토콜은,ProtocolA 프로토콜을 계승하고 있습니다. 그 때문에,ProtocolC 그럼,MethodA와 MethodC 하지만 선언되고 있다고 생각할 수 있습니다.

또,Test 클래스는,ProtocolB 와 ProtocolC 를 동시에 채용하고 있는 점에도 주목해 주세요. 선언 밖에 보유하고 있지 않는 프로토콜은 메소드명이 충돌해도 프로그램의 메소드 검색에는 영향이 없기 때문에 가능합니다. 다만, 다루려는 의도의 메소드가 프로토콜의 계승 관계 중에서 충돌했을 경우는 주의할 필요가 있겠지요.

Posted by tklee

클래스에 메소드를 추가한다

Objective-C 에는 클래스를 복수의 파일에 분할해 선언, 및 정의하기 위한 기능을 제공하고 있습니다. 이것을 카테고리라고 부르고, 카테고리를 이용하는 것으로 클래스의 메소드 선언을 분할할 수 있습니다. 예를 들면, 복수의 개발자가 공동으로, 동시 평행으로 동일 클래스를 구현하는 경우, 각각의 개발자에게는 클래스 가운데, 개발자가 담당하는 메소드를 카테고리에 분할합니다.

카테고리를 선언하려면 , 반드시 메인이 되는 클래스의 인터페이스 선언이 필요합니다. 클래스의 카테고리를 선언, 및 정의하는 경우는, 다음과 같은 구문을 지정합니다.

@interfae 클래스명 (카테고리명) { ...

@implementation 클래스명 (카테고리명) { ...

여기서 지정하는 카테고리명은, C 언어의 식별자의 명명 규칙에 따릅니다. 카테고리화하는 클래스는 반드시 먼저 메인이 되는 본체가 선언되어 있지 않으면 안됩니다.

카테고리는 통상의 클래스의 선언이나 정의를 닮아 있습니다만, 인스턴스 변수를 선언할 수 할 수 없기 때문에 주의해 주세요. 카테고리를 선언할 수 있는 것은, 인스턴스 메소드와 클래스 메소드 뿐입니다.

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

@interface Test : Object
- (void)WriteA;
@end

@interface Test (Fate)
- (void)WriteB;
@end

@implementation Test
- (void)WriteA {
	printf("I am the bone of my sword.\n");
}
@end

@implementation Test (Fate)
- (void)WriteB {
	printf("몸은 검으로 되어있다\n");
}
@end

int main() {
	id obj = [Test new];
	[obj WriteA];
	[obj WriteB];
	[obj free];

	return 0;
}

이 프로그램은 Test 클래스를 메인과 Fate라는 이름을 가지는 카테고리에 분할하고 있습니다. 실행 결과는, 상상대로입니다. Test 클래스의, 분할 떠날 수 있어 모든 카테고리는, 최종적으로 동일한 클래스로서 통합됩니다. 물론, 카테고리는 다른 파일로 선언해도 괜찮습니다. 그 경우, 카테고리를 선언하는 파일은 메인이 되는 클래스를 선언하는 헤더 파일을 인클루드 하고 있을 필요가 있습니다.

카테고리는, 기존의 완성하고 있는 클래스에 기능을 추가한다고 하는 목적으로 사용할 수도 있습니다. 다만, 인스턴스 변수를 추가하는 것은 할 수 없기 때문에, 기존의 클래스의 역할의 범위로의 확장이라는 것이 됩니다.

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

@interface Object (Write)
- (void)Write;
@end

@implementation Object (Write)
- (void)Write {
	printf("I am the bone of my sword.\n");
}
@end

int main() {
	id obj = [Object new];
	[obj Write];
	[obj free];

	return 0;
}

이 프로그램은 루트 클래스인 Object 클래스에 새로운 카테고리를 추가하고 있습니다. 덧붙여서, 카테고리의 메소드가 기존 클래스의 메소드와 충돌했을 경우, 카테고리의 메소드가 우선되어 기존의 메소드는 은폐 되어 버립니다. 계승에 의한 오버라이드(override)와는 다르기 위해, 은폐 된 메소드를 호출하는 수단이 없어져 버릴테니 주의해 주세요.

Posted by tklee

메소드를 함수로서 호출한다

실은, Objective-C 의 메소드의 실태는 C 언어의 함수와 같습니다. 평상시는 은폐 되고 있습니다만, 메소드와는 최초의 인수에 자신의 클래스를 참조하는 오브젝트를 받는 함수입니다.

메소드의 실체가 함수이다고 하는 사실은 C 언어와의 친화성이 지극히 높은 것을 의미하고 있습니다. 순수 C 언어로 기술된 라이브러리로부터 Objective-C 의 메소드를 호출하거나 오브젝트를 이용하는 것도 무리한 이야기는 아닙니다.

Objective-C 의 메소드는, 항상 IMP 형태로 정의됩니다. IMP 형태는, 헤더 파일로 다음과 같이 정의되고 있습니다.

typedef id (*IMP)(id, SEL, ...);

이 정의로부터도 알 수 있듯이, Objective-C에 선언된 모든 메소드는, 암묵적으로 id 형태와 SEL 형태의 인수를 가집니다. 제일 인수는, 메소드를 호출한 오브젝트를 나타내는 변수 self 입니다. 제2 인수는, 이 메소드의 실렉터를 나타내는 변수 _cmd 입니다. 모든 메소드에는, 이러한 숨겨진 인수가 반드시 존재합니다. 그 다음은, 메소드의 선언에 따라 인수가 결정되겠지요.

C 언어나, 어떠한 이유로 Objectiver-C로부터, 메소드를 함수로서 호출할 필요가 있는 경우, 메소드를 참조하는 함수에의 포인터를 취득하면 좋습니다. 메소드를 참조하는 IMP 형태의 포인터는 Object 클래스의 instanceMethodFor 클래스 메소드, 또는 methodFor 인스턴스 메소드로부터 얻을 수 있습니다.

+ (IMP)instanceMethodFor:(SEL)aSel;

- (IMP)methodFor:(SEL)aSel;

aSel 에는 대상 메소드의 IMP, 즉 함수에의 포인터를 취득하고 싶은 실렉터를 지정합니다. 메소드는 aSel로 지정된 실렉터가 특정하는 메소드의 포인터를 돌려줍니다.

포인터를 취득할 수 있으면, C 언어로부터에서도 인스턴스 메소드를 호출할 수 있게 됩니다. 함수의 포인터로서 직접 호출하기 위해, 메세지 통신보다 고속으로 되는 점도 특징이지요.

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

@interface Test : Object
- (void)Write;
@end

@implementation Test
- (void)Write {
	printf("I am the bone of my sword.\n");
}
@end

int main() {
	id obj;
	SEL method;
	IMP func;

	obj = [Test new];
	method = @selector(Write);
	func = [Test instanceMethodFor:method];
	func(obj , method);

	return 0;
}

이 프로그램은 Test 클래스의 인스턴스 메소드 Write 에의 포인터를 취득해, IMP 형태의 변수 func 에 보존하고 있습니다. [Test instanceMethodFor:method] 과 같은 메세지 부분은, [obj methodFor:method] 이라고 해도 의미는 같습니다. 이 메세지식이 돌려주는 IMP 형태의 메소드의 포인터를 사용하고, 직접 메소드를 호출하고 있습니다.

화상 처리나 멀티미디어 등, 루프 처리의 고속화등이 필요한 경우, 메세지 통신이나 실렉터 통신은 부담이 커집니다. 속도를 고집하는 프로그램이면, 필요에 따라서 IMP 를 취득해, 포인터로부터 메소드를 호출해도 좋을 것입니다.

Posted by tklee

메소드의 내부 표현

Objective-C 컴파일러는, 메소드를 특정하는 이름을 컴파일시에 내부 표현으로 변환합니다. 이, 메소드의 내부 표현을 셀렉터라고 부르고, 메세지의 송수신의 뒤편에서는, 이 셀렉터가 교환되고 있습니다. 메소드를 특정하기 위한 내부 표현에 대해서는 컴파일러에 의존하는 문제이며, 개발자가 알아야 할 범위가 아닙니다. 개발자에게 있어서 중요한 것은, 이 셀렉터를 SEL 형태로서 취급할 수 있다고 하는 사실입니다.

메소드가 어떠한 데이터에 변환되어 어떻게 식별되고 있는가는 문제가 아닙니다. 그러나, Objective-C는 이 내부 표현을 SEL 형태의 변수로서 취급하는 것을 보장하고 있습니다. 즉, SEL 형태의 변수에는, 메소드명을 식별하기 위해서 컴파일러가 할당한 특수한 코드를 보존할 수 있는 것이 됩니다.

메소드를 특정하는 실렉터는 @selector 컴파일러 지시문을 이용해 취득할 수 있습니다.

@selector ( method )

method 에는, 실렉터를 취득하고 싶은 메소드의 이름을 지정합니다. 지정한 메소드의 이름이 존재할지 어떨지는, 메소드를 호출할 때, 실행시에 판정되기 위해, 컴파일시에는 평가되지 않을 것입니다.

그럼, 취득한 실렉터의 값을 SEL 형태의 변수에 보존했다고 해서, 이것을 어떻게 이용할 수 있는 것입니까. 셀렉터가 메소드를 특정한다고 하는 성질이 있는 이상, 역시 최대의 이용 방법은, 함수에의 포인터와 같이, 동적으로 메소드를 특정하는 방법이지요. 셀렉터로부터 메소드를 호출하는 기능을 제공하는 것은 루트 클래스입니다.

Object 클래스에는, SEL 형태의 값을 받는 perform 메소드가 선언되고 있습니다. 이 메소드는, 인수로 받은 실렉터가 특정하는 메소드를 실행합니다.

- perform:(SEL)aSel;

- perform:(SEL)aSel with:anObject;

- perform:(SEL)aSel with:anObject1 with:anObject2;

aSel 에 호출하는 메소드의 셀렉터를 지정합니다. anObject,anObject1,anObject2 에는 메소드에 건네주는 인수를 지정할 수 있습니다.

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

@interface Test : Object
- (void)Write;
@end

@implementation Test
- (void)Write {
	printf("I am the bone of my sword.\n");
}
@end

int main() {
	id obj;
	SEL method;

	obj = [Test new];
	method = @selector(Write);
	[obj perform:method];

	return 0;
}

이 프로그램에서는, Write 메소드를 나타내는 실렉터를 SEL 형태의 변수 method 에 보존하고 있습니다. 그리고,Test 클래스의 인스턴스 obj 에 perform 메세지를 method를 인수로서 송신합니다. perform 메소드는, 주어진 실렉터로부터 실행해야 할 메소드를 산출해 실행합니다. 이 성질을 능숙하게 이용하면, 실행시에 호출해야 할 메소드를 상황에 따라 바꾸는 프로그램을 실현할 수 있습니다.

Posted by tklee

클래스를 나타내는 오브젝트

변수 선언등에서 클래스명을 지정해, 그 클래스형의 변수를 준비할 수 있었습니다. 그러나, 클래스 메소드를 호출하기 위해서, 메세지식에서 지정한 클래스명은, 실은 클래스의 형태를 나타내고 있는 것은 아닙니다. 메세지식에서 지정하는 것은, 항상 메세지를 송신하는 앞의 오브젝트입니다. 즉, 메세지에서 지정해 있던 클래스명은 오브젝트입니다.

클래스가 인스턴스를 생성하도록 올바르게 컴파일 된 클래스는, 스스로의 정보를 나타내는 클래스 오브젝트를 보유하고 있습니다. 이 클래스 오브젝트는 Class 형태로서 변수에 보존하는 것도 가능합니다. 클래스 오브젝트가 존재하지 않는 값은 Nil 이라고 하는 정수로 표현됩니다. 통상,Nil은 NULL 와 같게 0 을 나타냅니다.

클래스 오브젝트를 취득하려면 , 인스턴스가 존재하는 경우는, Object 클래스의 class 인스턴스 메소드의 반환값으로부터 취득합니다. 인스턴스가 존재하지 않는 상태로, 클래스명이 판명되어 있는 경우, 클래스명을 직접 지정해 클래스 오브젝트를 취득할 수도 있습니다. 통상, 클래스명은 이름으로서 인식됩니다만,  메세지 송신지 오브젝트의 지정으로 클래스명을 지정했을 경우에 한해서, 이 클래스의 클래스 오브젝트로서 인식됩니다. 메세지에서, 클래스명으로 지정한 클래스 오브젝트를 돌려주게 하려면 , 클래스 오브젝트에 class 메세지를 송신합니다. 이 메소드는 다음과 같이 선언되고 있습니다.

- (Class)class;

이 메소드가 돌려준 값이 Class 형태의 오브젝트입니다. 클래스 오브젝트는 변수에 보존할 수 있기 위해, 클래스 메소드를 호출하는 메세지도, 변수에 보존되고 있는 오브젝트에 따라서 결과가 다른 다양성을 도입할 수 있습니다.

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

@interface Test : Object
+ (void)Write;
- (id)init;
@end

@implementation Test
+ (void)Write {
	printf("I love you... so please do not love me.\n");
}
- (id)init {
	printf("You can be whatever.\n");
	return [super init];
}
@end

int main() {
	Class testClass = [Test class];

	[testClass Write];
	[[testClass new] free];
	[testClass free];

	return 0;
}

이 프로그램의 [Test class] 의 Test는, 실은 클래스형은 아니고 클래스 오브젝트를 메세지식으로 지정하고 있습니다. 메세지식은 Test 클래스의 클래스 오브젝트를 돌려주어, testClass 변수에 오브젝트를 보존하고 있습니다. 이후, 이 testClass 변수는, 메세지식에서 Test 클래스 오브젝트와 같은 의미를 가집니다.

이 위력은, 그 후,Write 클래스 메소드를 실행하기 위해서 [testClass Write] 메세지식에서 실현될 수 있다고 하는 부분에서 확인할 수 있겠지요. 클래스 오브젝트를 가리키는 변수로부터 Write 메세지를 송신하고 있기 때문에, 이 호출은 지극히 동적인 호출이 됩니다. 물론,alloc이나 new 메세지를 송신하고, 클래스 오브젝트로부터 인스턴스를 작성할 수 있습니다. 변수를 바꿔 넣는 것만으로, 호출하는 클래스 메소드나 생성하는 인스턴스를 변경할 수 있기 위해, 유연한 프로그램을 실현하기 위해서 이용할 수 있습니다.

Posted by tklee

클래스에 직결한 메소드

인스턴스 변수나 인스턴스 메소드는, 인스턴스가 존재하지 않으면 호출할 수 없습니다. 이것들은, 인스턴스에 관련된 메모리를 개별적으로 할당할 수 있기 위해 당연한 일입니다.

클래스 메소드는, 반대로 인스턴스에는 관계없이, 클래스 그 자체에 직결하고 있는 메소드입니다. 메소드는 인스턴스와 관련하고 있지 않기 때문에, 사실상 글로벌 함수랑 다름이 없습니다. 다른 점은, 호출할 때 메세지를 사용하고 메세지를 개입시켜 호출할 필요가 있는 점입니다.

클래스 메소드를 선언하는 것은, 메소드의 선언시로 지정하고 있던 마이너스 기호 - 가 플러스 기호 + 로 바뀌는것 외에 인스턴스 메소드와 같습니다. 다만, 클래스 메소드에는 인스턴스가 존재하지 않기 때문에, 암묵의 self 오브젝트를 사용할 수 없습니다.

메세지식으로부터 클래스 메소드를 호출하려면 , 인스턴스가 아닌 클래스명을 지정합니다. 지금까지, alloc 클래스 메소드를 사용해 온적이 있으므로, 클래스 메소드의 호출하는 방법에 대해서는 설명 불필요하겠지요.

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

@interface Test : Object
+ (void)Write;
@end

@implementation Test
+ (void)Write {
	printf("I love you... so please do not love me.\n");
}
@end

int main() {
	[Test Write];
	return 0;
}

이 프로그램의 Test 클래스에서 선언되고 있는 Write 메소드는 + 가 지정되어 있기 때문에 클래스 메소드라고 판단할 수 있습니다. 클래스 메소드의 호출에는 인스턴스는 불필요해서 main() 함수에서는 [Test Write] 의 형태로 클래스에 대해서 메세지를 송신하고 있습니다.


클래스 변수

인스턴스 메소드에 대해서 클래스 메소드가 존재한다면, 인스턴스 변수에 대해서 클래스에 직결하는 클래스 변수가 있어도 괜찮다고 생각할지도 모릅니다. Java 프로그래머등에는 static 메소드, 정적인 메소드라고 표현하는 것이 알기 쉬울 것입니다.

실은, Objective-C 에는 클래스 변수는 존재하지 않습니다. 그 클래스의 모든 인스턴스가 공유하는 변수라는 것을 갖고 싶은 경우, 클래스를 선언하는 헤더 파일내에 파일 스코프의 글로벌 변수를 준비해, 이 글로벌 변수를 인스턴스 메소드등에서 액세스 한다고 하는 형태로 클래스 변수를 실현합니다.

Posted by tklee

인스턴스 변수의 공개 범위

통상, 클래스는 구조체와는 다른 역할이 있기 위해, 데이터형의 제공만으로 클래스가 존재해야 하는 것이 아닙니다. 그러나, RGB 형식의 색을 표현하기 위한 클래스나, 좌표나 사이즈를 나타내는 클래스면, 메세지로부터 값을 얻는 처리는 약간 장황해서, 직접 인스턴스 변수와 교환할 수 있는 편이 좋다고 생각하는 개발자도 있겠지요.

원칙적으로는, 클래스의 인스턴스 변수에 외부로부터 액세스 가능하지 않고, 디폴트는 밖으로부터 인스턴스 변수에 액세스 할 수 없습니다. 기본적으로, 인스턴스 변수를 선언하는 클래스의 메소드 이외로부터 인스턴스 변수가 조작되면, 변수에 보존되고 있는 값의 정합성을 유지할 수 없게 되기 때문입니다. 외부로부터 인스턴스 변수에 액세스 하려면 , 우선 인스턴스 변수의 가시성을 명확하게 하지 않으면 안됩니다.

인스턴스 변수의 가시성은, 공개를 나타내는 public, 비공개를 나타내는 private, 서브 클래스로부터의 액세스를 허가하는 protected 로 나누어집니다. public 은, 클래스의 외부나 서브 클래스를 포함해 모든 장소로부터 인스턴스를 통해 액세스 되는 변수를 나타냅니다. private는, 반대로 인스턴스 변수를 선언한 클래스의 메소드만 액세스 가능한 변수입니다. 그리고, protected는 변수를 선언한 클래스와 그 클래스의 서브 클래스로부터 액세스 할 수 있는 변수를 나타냅니다.

선언하는 인스턴스 변수에 이러한 가시성을 지정하려면 , 컴파일러 지시문을 이용해 @public,@private,@protected 의 어느쪽이든을, 인스턴스 변수 선언 부분의 사이에 삽입합니다. 이러한 컴파일러 지시문이 출현하면, 그 이후에 선언된 인스턴스 변수는 모두, 지정된 가시성으로 설정됩니다. 가시성이 지정되어 있지 않은 경우, 인스턴스 변수는 protected로 설정됩니다.

@interface A : Object
{
	int z;	//protected
@public
	int a;	//public
@protected
	int b;	//protected
@private
	int c;	//private
	int d;	//private
}
@end

이와 같이, 가시성을 지정한 후에 선언된 인스턴스 변수는, 그 가시성을 가집니다. 가시성을 지정하는 컴파일러 지시문의 출현 순서, 출현 회수에 제한은 없습니다.

public 공개되고 있는 변수에, 클래스의 외부로부터 인스턴스를 통해 액세스 하려면 , 클래스형의 포인터 변수로부터 -> 연산자를 사용해 인스턴스 변수를 지정합니다. 이것은, 구조체에의 포인터로부터 멤버 변수에 액세스 하는 방법과 같습니다. 인스턴스 변수에의 액세스는 컴파일시에 형태 체크가 필요하기 위해 id 형태의 변수로부터는 액세스 할 수 없습니다.

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

@interface A : Object
{
@public
	int a;
@protected
	int b;
@private
	int c;
}
- (id)initWithA:(int)a int:(int)b int:(int)c;
- (void)WriteA;
@end

@interface B : A
- (void)WriteB;
@end

@implementation A
- (id)initWithA:(int)a int:(int)b int:(int)c {
	self->a = a;
	self->b = b;
	self->c = c;
	return self;
}
- (void)WriteA {
	printf("[A Write Method, a=%d, b=%d, c=%d]\n", a , b , c);
}
@end

@implementation B
- (void)WriteB {
	printf("[B Write Method, a=%d, b=%d]\n", a , b);
}
@end

int main() {
	B * objb = [[B new] initWithA:1000 int:100 int:10];
	printf("[main() scope, a=%d]\n" , objb->a);
	[objb WriteB];
	[objb WriteA];
	[objb free];
	return 0;
}

이 프로그램으로 선언하고 있는 A 클래스는, public 인스턴스 변수 a, protected 인스턴스 변수 b, private 인스턴스 변수 c를 선언하고 있습니다. 그리고, 이것들을 액세스 할 수 있는 것을 증명하기 위해서, main() 함수로부터 B 형태의 포인터 변수 objb를 통해 공개되고 있는 변수 a 에 objc->a 와같은 형태로 액세스 하고 있습니다.

A 클래스를 계승하는 서브 클래스 B에, WriteB 와같은 메소드를 선언해, 이 메소드의 구현으로 공개되고 있는 변수 a 인 서브 클래스로부터의 액세스가 허가되고 있는 변수 b 를 표시합니다. 다만, 서브 클래스로부터도 private 변수에 액세스 할 수 없기 때문에 c를 표시할 수 없습니다. 모든 변수에 액세스 할 수 있는 것은, 인스턴스 변수를 선언한 A 클래스 뿐입니다. A 클래스의 메소드 WriteA 에서는, 모든 인스턴스 변수를 표시하는 것이 가능합니다.


Posted by tklee

클래스형

Objective-C가 생성한 클래스의 인스턴스는 id 형태의 변수에 보존할 수 있었습니다. id 형태는 void *를 닮은 존재로, 클래스에 관계없이, 인스턴스를 보존하는 범용적인 오브젝트형입니다. 그 때문에, id 형태의 변수의 실체는, 컴파일시는 아니고 실행시에 판정된다고 하는 특징이 있습니다.

그러나, 실행시에 인스턴스를 조사하는 것은, 예를 들면 메소드를 호출하기 위해서, 오브젝트에 대해서 메세지를 송신해도, 그 메세지에 대응한 메소드가 존재하지 않는다고 할 가능성이 있습니다. 다음과 같은 프로그램은 그 전형적인 예이지요.

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

@interface A : Object
- (void) Write;
@end

@implementation A
- (void) Write {
	printf("I am the born of my sword.\n");
}
@end

int main() {
	id obj1 = [A new];
	id obj2 = [Object new];

	[obj1 Write];
	[obj2 Write];	//실행시 에러

	return 0;
}

이 프로그램은 선언한 A 클래스의 인스턴스를 생성해 Write 메세지를 송신하고 있습니다. 이와 같이 Object 클래스의 인스턴스를 생성해, Write 메세지를 송신하고 있습니다. Object 클래스에는 Write 메소드 등은 존재하지 않습니다만, 컴파일 할 수 있습니다. 이것은, Objective-C 의 메세지의 호출이 실행시에 판정되는 것을 나타내고 있습니다.

컴파일 된 프로그램을 실행하면, obj1 변수에 대해서 Write 메세지의 송신은 성공합니다만, obj2 변수는 Object 클래스의 오브젝트이므로 Write 메소드는 존재하지 않습니다. 그 때문에, obj2 변수에 대해서 Write 메세지를 송신해도, 실행시 에러가 발생합니다.

이와 같이, id 형태는 범용적이고, 오브젝트라고 하는 단위로 클래스형의 인스턴스 데이터를 참조하려면 편리합니다만, 특정의 클래스를 상정해 취급하는 경우에는 적합하지 않습니다. 특히, 대규모 프로그램이 되고, 많은 인스턴스를 생성하는 경우는, id 형태를 대량으로 선언하면, 어느 변수에 어느 클래스형의 오브젝트를 보존하고 있는지 어떤지가 모르게 되어 버립니다.

형태의 판정을 실행시가 아니고, 컴파일시에 실시할 수 있으면, 컴파일러가 형태를 체크하는 정보로서 이용할 가능성이 있습니다. 구현에 의존하는 문제입니다만, 형태가 컴파일시에 판명되면, 메세지를 조사하고, 대상의 클래스형으로 지정한 메소드가 존재할지를 조사해 필요에 따라서 경고를 뿌립니다. 컴파일시에, 정적으로 클래스형을 판정시키려면 ,id 형태가 아닌 클래스명을 형태로 한 클래스형의 포인터를 사용해 인스턴스를 참조합니다. 예를 들면, Point 클래스 전용의 변수는 Point 형태에의 포인터로서 선언합니다.

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

@interface A : Object
- (void)Write;

@end


@implementation A
- (void)Write {
	printf("A . Write Method\n");
}
@end

int main() {
	A * obja = [A new];
	[obja Write];
	[obja free];
	return 0;
}

이 프로그램은, 선언된 A 클래스의 인스턴스를 작성해, A * 형태의 변수 obja 에 보존하고 있습니다. 지금까지는 id 형태에 오브젝트를 보존해 왔습니다만, 이 프로그램에서는 명시적으로 A 클래스형인 것을 지정해 있습니다.

클래스형의 포인터는, 메모리상의 실체인 인스턴스의 주소를 보존하기 위해서 이용됩니다. id 형태도 인스턴스의 주소를 보존하기 위한의 것입니다만, 형태로서는 클래스형을 지정하는 편이 명확합니다. 통상, Objective-C 의 클래스형 변수는 구조 체형의 변수와 같이, 정적인 변수로서 선언할 수 없습니다. 클래스형의 변수는 반드시 포인터형으로서 다루어져야 하는 것입니다.

SmallTalk를 기반으로하는 Objective-C 언어에서는, 비교적 id 형태가 이용되고 있는 경향이 있습니다만, 현대의 객체 지향 언어의 표준적인 기술 방법으로부터 생각하면, 클래스형을 명기한 변수를 사용하는 것이 좋을 것입니다. id 형태를 사용하는 것은, alloc 이나 init 과 같은, 돌려주는 값이 다양화되고 있는 메소드 등에 한정해야 합니다.

그럼, 클래스형의 변수는, 그 클래스형의 인스턴스에의 포인터 밖에 대입할 수 없는 것일까요. C 언어의 원리를 생각하면, 결국은 포인터에 지나지 않기 때문에 그러한 일은 없습니다.


Posted by tklee

인스턴스 파기

C 언어에서는, 실행시에 동적으로 확보한 메모리 영역은, 반드시 해제하지 않으면 안됩니다. 표준 함수의 malloc() 이 확보한 메모리 영역은 free() 함수로 해방하지 않으면, 메모리 영역이 불필요하게 되어, 메모리 참조가 없어진 후에도, 어플리케이션을 위해서 계속 대기하기 위해 , 결과적으로 메모리 리크가 됩니다.

Objective-C도 같이 Java나 Microsoft .NET와 같이, 메모리를 자동적으로 해제하는 기능은 존재하지 않습니다. 거기서, Object를 루트 클래스로 하는 alloc가 확보한 인스턴스는, 불필요하게 된 시점에서 free 메세지를 송신해 해제하지 않으면 안됩니다.

free 메세지는, 그 인스턴스가 확보하고 있는 메모리 영역을 해제해, 인스턴스를 완전하게 파기하기 위해서 송신됩니다. 초기화에 대해서, 인스턴스가 파기되는 타이밍에 어떠한 처리가 필요한 경우는 free 메세지를 오버라이드(override) 하면 좋을 것입니다. 다만, 오버라이드(override)때는 슈퍼 클래스에 free를 송신하는 것을 잊지말아야됩니다.

#import <stdio.h>

#import <objc/Object.h>

@interface Test : Object
-(id)init;
-(id)free;
@end

@implementation Test
- (id)init {
	id obj = [super init];
	printf("init method\n");
	return obj;
}
- (id)free {
	printf("free method\n");
	return [super free];
}
@end

int main() {
	id obj = [Test new];
	[obj free];

	return 0;
}

이 프로그램의 main() 함수 부분에서는, 인스턴스를 new 메세지로 생성 및 초기화해, 직후에 free 메세지로 파기하고 있습니다. init 와 free 메소드를 오버라이드(override) 해서, 메소드가 실행된 것을 증명하기 위해서 문자열을 출력하고 있습니다.

통상, 초기화는 루트 클래스로부터 행해져 해제은 리프로부터 행해집니다. init 메소드에서는, 메소드의 선두에서 부모클래스의 init 메소드를 호출하고, free 메소드에서는 메소드의 말미에서 부모클래스의 free 를 호출합니다.


Posted by tklee

오브젝트의 초기화

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

일반적인 객체 지향형 언어에서는, 오브젝트를 안전하게, 확실하게 초기화하기 위한 방법으로서 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

메소드의 덧쓰기

클래스의 계승으로 특필해야 하는 것은, 단순한 기능의 계승이 아닙니다. 클래스의 계승은 추상적인 기능을 구체적인 처리로 이행하는, 기능의 덧쓰기에 있다고 생각할 수 있습니다. 이, 기능의 덧쓰기란, 슈퍼 클래스의 메소드를 은폐 해, 서브 클래스의 메소드를 우선적으로 호출하게 하는, 메소드의 덧쓰기에 의해서 실현됩니다. 이것을, 일반적으로는오버라이드(override)라고 부릅니다.

메소드의 오버라이드(override)는, 슈퍼 클래스의 메소드와 같은 이름, 같은 반환값형, 같은 인수의 메소드를 서브 클래스에서 정의하는 것으로 실현됩니다. 일반적으로는, 이러한 메소드명과 메소드의 반환값이나 인수형 전체를 서명이라고 부릅니다. 메소드의 오버라이드(override)는, 서명이 같은 메소드를 서브 클래스에서 정의하는 것으로 실현됩니다.

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

@interface SuperClass : Object
- (void)method;
@end

@interface SubClass : SuperClass
-(void)method;
@end

@implementation SuperClass
- (void)method {
	printf("SuperClass.method\n");
}
@end

@implementation SubClass
- (void)method {
	printf("SubClass.method\n");
}
@end

int main() {
	[[SuperClass alloc] method];
	[[SubClass alloc] method];
	return 0;
}

이 프로그램에서는, 슈퍼 클래스인 SuperClass 클래스와 서브 클래스인 SubClass 클래스에서 서명이 동일한 method 메소드를 선언, 및 정의하고 있습니다. Objective-C는 이러한 메소드의 충돌은 compile error가 아닌, 메소드의 덧쓰기, 즉 오버라이드(override) 하는 것으로 해결합니다.

오버라이드(override) 된 메소드의 효과는 main() 메소드의 실행 결과를 보면 확인할 수 있습니다. 인스턴스가 SuperClass 의 경우는 SuperClass 클래스의 method 메소드가 불려지며 인스턴스가 SubClass 클래스의 것이면 SubClass 의 method 메소드가 불려 집니다. 이것은 SubClass가 SuperClass 의 method 메소드를 숨겨, 새롭게 정의한 스스로의 method 메소드로 오버라이드(override) 한 것을 나타냅니다.

오버라이드(override)는, 결과적으로 프로그램의 다양성을 실현합니다. 메소드를 호출하는 코드는, 코드를 변경하지 않아도, 인스턴스에 의해서 호출해야 할 적절한 메소드를 선택할 수 있게 됩니다. 다음과 같은 코드를 상정해 주세요.

void CallMethod(id obj) {
	[obj method];
}

int main() {
	CallMethod([SuperClass alloc]);
	CallMethod([SubClass alloc]);

	return 0;
}

이 코드에서는, CallMethod() 함수로 id 형태의 오브젝트를 인수로부터 받고 있습니다. 이 함수에서는, 받은 오브젝트의 method 메소드를 호출하고 있습니다.

그러나, 이 함수가 결과적으로 어느 메소드를 호출할까는, 건네받는 오브젝트의 인스턴스에 따라서 다릅니다. 함수가 호출하는 메소드는, 실행시기가 아니면 최종적인 판정은 할 수 없습니다. C 언어에서는,switch 문장등에서 분기 시켜 호출해야 할 함수를 선별하거나 실행시에 동적으로 함수를 호출하는 구조를 실현하기 위해서, 함수에의 포인터등을 이용하지 않으면 안됩니다. Objective-C는 그러한 독자적인 설계는 필요없고, 동적인 호출에 의한 코드의 다양성을 언어 레벨로 서포트하고 있습니다.

그런데 , 슈퍼 클래스의 메소드가 완전하게 은폐 되어도 곤란한 일이 있습니다. 특히, 메소드의 오버라이드(override)의 목적이 기능의 확장인 경우, 확장하는 부분만큼을 서브 클래스에서 기술해, 나머지의 기본적인 처리 부분은 슈퍼 클래스의 오버라이드(override) 된 메소드에 맡겨야 합니다. 이것을 실현하려면 , 서브 클래스의 오버라이드(override) 한 메소드로부터, 슈퍼 클래스의 오버라이드(override) 된 메소드를 호출할 필요가 있습니다.

슈퍼 클래스의 메소드를 서브 클래스로부터 명시적으로 호출하려면 super 를 이용합니다. super 는,슈퍼 클래스를 보유하는 클래스의 인스턴스 메소드내의 메세지에서만 이용할 수 있는 특별한 이름으로, 슈퍼 클래스의 메소드를 호출하는 것을 명시합니다. self 는 인스턴스 메소드내에 존재하는 암묵적인 id 형태의 변수였지만, super 는 변수는 아닌 것에 주의해 주세요.

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

@interface SuperClass : Object
{
	int x;
}
- (void)method;
@end

@interface SubClass : SuperClass
-(void)method;
@end

@implementation SuperClass
- (void)method {
	printf("SuperClass.method\n");
}
@end

@implementation SubClass
- (void)method {
	printf("SubClass.method %d\n" , x);
	[super method];
}
@end

int main() {
	[[SubClass alloc] method];
	return 0;
}

이 프로그램에서는 SuperClass 의 메소드를 오버라이드(override) 하고 있는 SubClass 의 method 메소드로부터, 슈퍼 클래스의 오버라이드(override) 된 method 메소드를 호출하기 위해서 super 를 이용하고 있습니다. 실행 결과로부터, SubClass 의 method 로부터 SuperClass 의 method를 호출하고 있는 것을 확인할 수 있겠지요.


Posted by tklee

기능의 확장

객체 지향의 설계에서는, 한 번 작성된 시스템(클래스)는 재이용 가능해야 한다고 생각되고 있습니다. Objective-C에서 올바르게 설계된 클래스는, 간단하게 그 기능을 확장할 수 있습니다. 그것도, 자신이 만든 클래스에서던, 타인이 만든 클래스에서던 관계없이 이용할 수 있습니다.

우리는 지금까지 루트 클래스의 Object 클래스를 계승해 왔습니다만, 우리가 독자적으로 작성한 클래스를, 한층 더 다른 클래스에 계승시킬 수도 있습니다. 클래스를 계승할 때, 새롭게 생성되는 클래스를 서브 클래스라고 부르고, 계승원의 클래스를 슈퍼 클래스라고 부릅니다.

서브 클래스는 슈퍼 클래스의 기능을 그대로 계승하기 위해, 슈퍼 클래스에서 정의되고 있는 메소드를 이용할 수 있습니다. 이것에 덫붙혀, 필요한 기능을 한층 더 추가하는 것이 서브 클래스의 역할입니다.

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

@interface SuperClass : Object
- (void)methodA;
@end

@interface SubClass : SuperClass
-(void)methodB;
@end

@implementation SuperClass
- (void)methodA {
	printf("SuperClass.methodA\n");
}
@end

@implementation SubClass
- (void)methodB {
	printf("SubClass.methodB\n");
}
@end

int main() {
	id obj = [SubClass alloc];
	[obj methodA];
	[obj methodB];
	
	return 0;
}

이 프로그램으로 정의하고 있는 SubClass 클래스는 SuperClass 클래스를 계승하고 있습니다. 이 때, SubClass는 SuperClass 의 서브 클래스이며, SuperClass는 SubClass 의 슈퍼 클래스이다고 표현할 수 있습니다. SubClass는 SuperClass 의 기능을 계승하고 있기 때문에, SubClass의 인스턴스는 SuperClass 의 기능을 그대로 이용할 수 있습니다.

이 프로그램에서는, SuperClass의 methodA 메소드를, SubClass의 methodB 를 선언하고 있습니다. SubClass가 methodB 를 이용할 수 있는 것은 당연합니다만, main() 메소드의 코드를 보고 확인할 수 있듯이, SubClass 의 인스턴스는 슈퍼 클래스의 SuperClass에 선언된 methodA 를 호출할 수도 있습니다.

클래스의 인스턴스를 작성하기 위한 alloc 메소드는 선언하지 않고 호출할 수 있었습니다만, 이것도 Object 루트 클래스를 계승하고 있었기 때문에 이용할 수 있었습니다. 사실 Object 루트 클래스를 계승하지 않으면 alloc 메소드를 이용할 수 없습니다.

원문 http://wisdom.sakura.ne.jp/programming/objc/objc5.html
번역의 귀차니즘으로 번역기 돌린 후 원문과 대조하면서 어색한 부분만 재수정한겁니다.
어휘 사용에서 다소 어색한 부분이 있더라도 양해바랍니다.

Posted by tklee

accessor method

클래스의 선언과 정의를 올바르게 실시해, Object 루트 클래스의 alloc 메소드를 호출하면, 클래스의 인스턴스가 생성됩니다. 클래스의 정의는 구조체의 선언과 같은 것으로, 인스턴스의 생성하는 것은, 구조 체형의 변수를 정의해 구조체를 표현하기 위해서 필요한 메모리를 할당하는 행위를 닮아 있습니다. 다만, 클래스는 멤버 변수와 그것을 처리하는 전용의 함수를 관련짓고 있는 점으로, 구조체와 크게 다릅니다.

전회에서, 클래스를 선언해, 메소드를 정의하고 그것을 호출하는 것을 실시했습니다. 전회의 메소드는 인수나 반환값을 교환하지 않는 지극히 단순한 것이었지만, 본 회에서는 보다 복잡한 메소드를 실현하기 위해서, 메소드의 상세를 설명합니다. 특히, 메소드는 함수와는 달리, 설계론의 개념으로부터 인스턴스나 클래스의 역할에 관련지을 수 없으면 안됩니다.

예를 들면, 2차원 좌표의 점을 나타내는 Point 클래스를 작성한다고 합니다. 이 경우, Point 클래스는 좌표 X 와 Y 를 제공하지 않으면 안됩니다. Point 클래스는 int 형태의 인스턴스 변수 x 과 y 를 선언해, 이러한 값을 제어하는 메소드군을 제공할 필요가 있습니다.

원칙으로서 클래스의 인스턴스 변수는 클래스의 외부에서는 액세스 할 수 없습니다. 왜냐하면, 변수가 자유롭게 변경되어 버리면, 변수의 의미나 입출력하는 데이터의 사양이 변경되었을 경우 등, 보수성이나 코드 전체의 유연성이 저하해 버리기 때문입니다. 거기서, Objective-C는 변수에 액세스 하기 위해서 메소드를 사용합니다. Point 클래스의 기본적인 선언은, 다음과 같이 되겠지요.

@interface Point : Object
{
	int x, y;
}
- (void)setPoint:(int)ptx:(int)pty;
- (int)getX;
- (int)getY;
@end

이 클래스의 선언으로는, 좌표를 보존하기 위한 변수 x 와 y 를 선언해, 한층 더 이 변수에 클래스의 외부로부터 액세스하기 위한 메소드를 선언하고 있습니다. setPoint 메소드는 x 와 y 의 값을 변경하기 위한 메소드로, getX 와 getY 메소드는, 각각의 변수를 취득하기 위한 메소드입니다. 이러한 메소드를, 특히 accessor method라고 부릅니다. 또, 이와 같이 인스턴스의 성질에 관한 외부에 제공하기 위한 정보를, 특히 프롭퍼티라고 부르기도 합니다.

인수를 받는 메소드는, 인수형을 ( ) 의 안으로 지정해, 바로뒤에 가인수의 이름을 지정합니다. 이 때의 ( ) 는 캐스트식과 같고, 형태를 지정하지 않는 경우는 반환값과 같게 id 형태가 디폴트로 되어 있습니다. 복수의 인수가 있는 경우, 한번더 콜론 : 을 이어 지정합니다. 보다 구체적으로는, 메소드의 선언은 다음과 같이 됩니다.

- (반환값형)메소드명 : (인수형) 변수명 라벨 : ....

복수의 인수를 받는 메소드는, 변수명의 뒤에 라벨을 지정하는 일이 있습니다. 비교적,Objective-C 의 세계에서는 라벨을 지정하는 것이 습관이 되고 있습니다만, 이 라벨과 콜론은 메소드명의 일부로서 인식됩니다.

라벨과 코론이 이름의 일부로서 인식되고 있기 때문에,Objective-C는 같은 반환값, 인수, 메소드명을 가지는 메소드를 구별해 호출할 수 있습니다. 예를 들면 (void)setPoint:(int):(int) 그렇다고 하는 메소드와 (void)setPoint:(int) label:(int) 메소드는 다른 메소드로서 선언, 정의할 수 있습니다.

인수 첨부의 메소드를 호출하려면 , 메세지에서 메소드명에 있는 인수에 건네주는 값을 지정합니다. 라벨을 지정하고 있지 않는 전자의 setPoint 의 경우 [obj setPoint:x:y] 의 형태로 호출하는데 대해, 라벨 첨부의 메소드는 [obj setPoint:x label:y] 의 형태로 호출합니다. 인수의 수나 라벨만 다르면, 메소드명이 같아도 구별해 호출할 수 있는 구조입니다. 특정의 기능을 제공하는 메소드를, 여러가지 형태에 대응해 제공하고 싶은 경우에 라벨이 필요합니다. setPoint 메소드를,int 형태 이외의 정수에도 대응시키고 싶은 경우 등은 중요하겠지요.

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

@interface Point : Object
{
	int x, y;
}
- (void)setPoint:(int)ptx int:(int)pty;
- (int)getX;
- (int)getY;
@end

@implementation Point
- (void)setPoint:(int)ptx int:(int)pty {
	x = ptx;
	y = pty;
}
- (int)getX {
	return x;
}
- (int)getY {
	return y;
}
@end

int main() {
	id point1 , point2;
	point1 = [Point alloc];
	point2 = [Point alloc];

	[point1 setPoint:16 int:32];
	[point2 setPoint:256 int:128];

	printf("point1:X=%d, Y=%d\n", [point1 getX] , [point1 getY]);
	printf("point2:X=%d, Y=%d\n", [point2 getX] , [point2 getY]);
	return 0;
}

이 프로그램의 Point 클래스는,2 개의 정수형 인수를 받는 setPoint:int: 메소드와 설정되어 있는 좌표를 돌려주는 getX,getY 메소드를 선언하고 있습니다. setPoint 메소드의 선언은 setPoint(int)ptx:(int)pty 에서는 문제 없습니다만, 장래 setPoint 를 확장하는 것을 생각했을 경우는, 라벨을 지정해야 겠지요.

main() 메소드에서는 alloc 메소드를 이용해 Point 클래스의 인스턴스를 2 개 생성하고 있습니다. point1 변수와 point2 변수가 가리키는 인스턴스는 차이가 나기 위해, 각각의 인스턴스로 설정한 값은, 개별의 메모리에 보존되고 있는 것이, 출력 결과로부터 확인할 수 있습니다.


암묵의 self

메소드의 스코프 범위내에는, 가인수나 선언된 변수 이외에, 암묵의 변수로서 self가 정의되고 있습니다. self 변수는 id 형태로, 항상 메소드를 호출한 인스턴스를 참조하고 있습니다. 즉, 메소드를 실행하고 있는 오브젝트 자신을 나타내는 변수가 self 입니다. (C++의 this 라고 생각하면 됩니다)

self 는 메소드 이외의 장소에서는 사용하지 못하고, 항상 메소드 실행 코드 부분에서만 암묵적으로 존재하고 있습니다. self 를 이용하는 것에 의해서, 오브젝트의 인스턴스 변수에 오브젝트로부터 참조할 수 있습니다. 이것은, 메소드의 가인수의 변수명이 인스턴스 변수명을 가려버렸을 때 등에 이용할 수 있습니다. accessor method에서는, 특히 중요한 존재가 되겠지요.

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

@interface Point : Object
{
	int x, y;
}
- (void)setPoint:(int)ptx int:(int)pty;
- (int)getX;
- (int)getY;
@end

@implementation Point
- (void)setPoint:(int)x int:(int)y {
	self->x = x;
	self->y = y;
}
- (int)getX {
	return x;
}
- (int)getY {
	return y;
}
@end

int main() {
	id point1 , point2;
	point1 = [Point alloc];
	point2 = [Point alloc];

	[point1 setPoint:32 int:64];
	[point2 setPoint:256 int:128];

	printf("point1:X=%d, Y=%d\n", [point1 getX] , [point1 getY]);
	printf("point2:X=%d, Y=%d\n", [point2 getX] , [point2 getY]);
	return 0;
}

이 프로그램의 setPoint:int: 메소드는, 가인수로 선언한 이름이 인스턴스 변수 x 와 y 에 충돌하고 있습니다. 이것 자체는 문제가 아닙니다만, 이 메소드의 스코프 범위에서는 인스턴스 변수가 가려져 버립니다. 거기서, 인스턴스 변수에 액세스 하는 수단으로서 현재의 메소드를 실행하고 있는 오브젝트를 참조하는 self 변수를 사용하고 있습니다. 클래스의 외부로부터 인스턴스 변수에 참조할 수 없습니다만, 메소드는 클래스의 내부이므로, 오브젝트로부터 직접 인스턴스 변수에 액세스 할 수 있습니다. 프로그램에서는 self->x 그렇다고 하는 형태로, 인스턴스 변수에게 건네진 데이터를 보존하고 있습니다.

또, 메소드로부터 같은 인스턴스가 다른 메소드를 호출하는 경우에도 self가 필요하게 됩니다. 몇개의 메소드가, 기능의 일부를 공유하고 있는 설계등에서는, 동일 클래스의 다른 메소드를 호출하는 것이 필요하게 되는 일이 있습니다.

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

@interface Test : Object
- (void)methodA;
- (void)methodB;
@end

@implementation Test
- (void)methodA {
	printf("method A\n");
	[self methodB];
}
- (void)methodB {
	printf("method B\n");
}
@end

int main() {
	[[Test alloc] methodA];
	return 0;
}

이 프로그램의 Test 클래스에서는,methodA 메소드로부터 methodB 메소드를 self 오브젝트를 사용해 호출하고 있습니다. self 하 methodA (을)를 실행하고 있는 인스턴스이므로, 동일 인스턴스의 methodB (을)를 호출하고 있는 것이 됩니다.

원문 http://wisdom.sakura.ne.jp/programming/objc/objc4.html
번역의 귀차니즘으로 번역기 돌린 후 원문과 대조하면서 어색한 부분만 재수정한겁니다.
어휘 사용에서 다소 어색한 부분이 있더라도 양해바랍니다.

Posted by tklee

컴파일러 지시문

Objective-C 언어는 객체 지향형 언어이므로, 재이용 가능한 데이터와 제어를 패키지화한 템플릿을 제공할 수 있습니다. 이것을, 일반적으로 클래스라고 부르고, 클래스는 메모리 실체를 생성하기 위한 정보입니다.

클래스의 개념은, C 언어의 구조체를 발전시킨 것으로, 구조체와 같은 데이터에 덧붙혀, 데이터와 제어(함수)를 관련짓고 있습니다. 그 때문에, 코드는 항상 데이터를 처리하는 전용의 함수를 안전하게 호출할 수 있어서 일련의 기능을 하나의 하부조직으로서 제공할 수 있습니다.

클래스를 이용하려면 , 구조체와 같이 우선 선언이 필요합니다. Objective-C 언어의 클래스의 선언은, 예약어(reserved word)나 구문이 아닌 컴파일러 지시문을 이용합니다. 컴파일러 지시문은, 컴파일러에 Objective-C에 확장된 클래스의 선언이나 실장 등에 이용되는 @ 마크로 시작되는 특징을 가집니다.

클래스를 선언하는 컴파일러 지시문은 @interface 로 시작해서 @end 로 끝납니다. 이러한 사이에, 클래스에 관련지을 수 있는 변수 영역과 함수를 선언합니다. Objective-C 의 클래스 선언과 정의의 관계는 다른 언어에 비해 복잡해서, 처음은 당황할지도 모릅니다.

@interface 클래스명 : 부모클래스명
{
	인스턴스 변수 선언
	...
}
메소드 선언
@end

이 시점에서,C 언어계의 객체 지향 언어와는 선언 방법이 꽤 다른 것이 많아집니다. 인스턴스 변수란, 클래스에 관련된 변수로, 구조체의 멤버와 같은 존재입니다. 다만, 구조체와는 달라 인스턴스 변수에 클래스의 외부로부터 액세스 할 수 없습니다. 인스턴스 변수를 이용할 수 있는 것은, 원칙으로서 클래스에 관련된 메소드만으로 되어 있습니다. 인스턴스 변수가 존재하지 않는 경우는 인스턴스 변수 선언과 { } 를 생략 할 수 있습니다.

메소드란, 클래스에 관련지을 수 있었던 함수입니다. 통상의 함수와의 차이는, 메소드는 메소드를 호출한 클래스의 실체에 관련지을 수 있고 있기 때문에, 직접 인스턴스 변수에 액세스 할 수 있습니다.

클래스명 바로뒤에 부모클래스를 지정하는 것이 있는데, 부모클래스란 무엇일까요? 객체 지향은, 한 번 정의된 기능을 확장하는 계승으로 불리는 기능을 제공하지 않으면 안됩니다. 계승 관계는 자주 생물의 진화론에 비유할 수 있어 개나 고양이등의, 여러가지 실체에 공통되는 추상적인 기능을 포유류 클래스로서 정의해, 개클래스나 고양이 클래스는 포유류 클래스를 계승하도록 프로그램 합니다.

부모클래스를 지정하지 않는 클래스는, 다른 클래스의 정점이 되기 위해 루트 클래스로 불립니다. Java나 .NET 그럼, 플랫폼이 기본이 되는 루와 클래스를 제공하는 구조를 채용해,C++ 언어는 루트 클래스를 정하지 않고 루트 클래스를 개발자가 개발할 수 있도록 설계되고 있습니다. Objective-C는 그 중간적인 형태로, 원칙적으로는 루트 클래스에서 개발할 수 있습니다만, 사실상은 컴파일러등의 처리계가 루트 클래스를 제공하지 않으면 안됩니다.

루트 클래스는 처리계에 따라서 다릅니다만 GCC 이면 Object 클래스가, Mac OS X 의 Cocoa 환경이면 NSObject 클래스가 루트 클래스가 됩니다. 독자적인 루트 클래스를 개발하는 경우이면 친클래스를 지정하지 않습니다만, 통상은 처리계가 제공하는 루트 클래스를 계승합니다. 루트 클래스를 개발하는 경우만, 선언은 다음과 같이 되겠지요.

@interface 클래스명
{
	인스턴스 변수 선언
	...
}
메소드 선언
@end

왜, 루트 클래스를 계승하지 않으면 한되는지라고 하면, 루트 클래스는 클래스가 정상적으로 동작하기 위해서 필요한 기본적인 기능을 제공하는 역할이 있기 때문에입니다. 루트 클래스가 존재하지 않으면, 클래스의 실체를 작성하기 위한 번잡한 수속을 모든 클래스에서 기술해야 하게 되어 버립니다. 히프메모리의 확보나 해방등의 기본적인 처리 수속은, 루트 클래스에 맡겨 버리는 것으로, 개발자는 개발 목적의 기능을 기술하는 것에 집중할 수 있습니다.

인스턴스 변수의 선언은 통상의 변수와 같습니다만, 메소드 선언은 함수의 선언과는 많이 다릅니다. 이 메소드 선언 사양은 C++ 에 비해 Objective-C는 C 언어 프로그래머에게 어색하게 받아 들여지는 큰 요인의 하나라고 생각할 수 있습니다. 메소드 선언은 다음과 같이 됩니다.

- (반환값형) 메소드명 : 가인수 리스트 ... ;

왜, 함수의 선언이랑 다른지가 C 언어와의 하이브리드 언어로서의 사양상의 문제가 부각되고 있습니다. 최초의 마이너스 기호 - 는 이 메소드가 클래스의 실체인 오브젝트에 관련하는 것을 나타내고 있습니다. 통상의 메소드는 - 기호로 좋습니다만, 오브젝트는 아니고 클래스에 관련하는 메소드의 선언이면 + 기호를 지정합니다. - 기호의 메소드를 인스턴스 메소드라고 불러,+ 기호로 시작되는 메소드를 클래스 메소드라고 부릅니다. 인스턴스 메소드와 클래스 메소드의 차이는 나중에 자세하게 설명합니다만, 일반적인 메소드는 - 로부터 시작되는점 기억해 주세요.

반환값형을 봐도 알수있듯이, 메소드 선언에서는 형태를 ( ) 로 둘러쌉니다. 메소드가 인수를 받는 경우는, 콜론 : 에 이어 가인수 리스트를 지정합니다. 가인수 리스트도, 통상의 변수 선언과는 달리, 변수형을 ( ) 의 안으로 지정해, 계속해서 변수명을 지정합니다.

C 언어의 함수의 디폴트의 반환값은 int 형태였지만, 메소드의 반환값의 디폴트는 id 형이라고 하는 Objective-C에 추가된 오브젝트를 나타내는 범용형으로 되어 있습니다. 반환값형은 생략 할 수 있습니다만, 통상은 생략하기 보다는, 명시적으로 선언합니다. 반환값을 돌려주지 않는 경우는 (void) 를 지정합니다.

@interface Test : Object
- (void)method;
@end

이 Test 클래스의 선언은, 반환값을 돌려주지 않고, 인수를 받지 않는 method 메소드를 선언하는 단순한 클래스를 선언하고 있습니다. 클래스명이나 메소드명의 명명 규칙은, C 언어의 변수나 구조체명의 명명 규칙과 같고, 한번에 식별할 수 있는 알파벳으로부터 시작되는 식별자가 아니면 안됩니다. 다만, 습관적으로 클래스명은 대문자로부터 시작하고 메소드명은 소문자로부터 시작합니다.

그런데, 클래스와 메소드를 무사하게 선언할 수 있었습니다만, 메소드는 메소드의 이름과 형태만을 선언한 것만으로 처리 코드를 기술하는 정의가 존재하지 않습니다. 실은, Objective-C는 클래스의 선언과 정의가 완전하게 분리되어 있고, 클래스의 선언으로는 인스턴스 변수와 메소드의 선언 밖에 실시할 수 없습니다. 클래스를 선언으로부터 정의로 구체화하려면 @implementation 컴파일러 지시문을 이용해 클래스를 작성하지 않으면 안됩니다.

@implementation 클래스명
메소드 정의
...
@end

@implementation 컴파일러 지시문에서는 @interface에 선언한 클래스의 실장을 기술합니다. 선언한 메소드는, 이 장소에서 정의되지 않으면 안됩니다. 클래스에서 메소드가 선언되어 있지 않은 경우,@implementation 에 기술하는 것은 아무것도 없습니다만, 정의되어 있지 않은 클래스는 @implementation에 명시적으로 실장하지 않으면 안됩니다.


클래스의 인스턴스화

클래스를 선언, 및 정의를 하면, 클래스를 이용할 수 있게 됩니다만, 그대로는 사용할 수 없습니다. 클래스를 이용하기 위해서는 필요한 메모리 영역을 할당해 적절한 초기화를 실시해 처음 클래스가 정상적으로 동작하게 됩니다. 클래스의 선언 정보에 근거하고, 메모리를 할당하는 것을 인스턴스화라고 부르고, 확보된 메모리 영역을 인스턴스라고 부릅니다. 인스턴스라고 부르는 경우, 통상은 클래스의 정보에 근거해 확보된 실체를 나타냅니다만, 일반적으로는 오브젝트라고도 불립니다.

C++ 언어나 Java 언어 등, 일반적인 객체 지향형 언어이면 new 연산자가 존재해, 이 연산자가 클래스의 선언 정보에 근거해 인스턴스를 생성해, 초기화해 줍니다. 그러나, 놀랄 만한 것이 Objective-C 언어는, 인스턴스의 생성을 포함해서 클래스가 실시하지 않으면 안 되도록 정해져 있습니다. 그런데 , 클래스의 인스턴스화에는, 오브젝트에 필요한 사이즈를 조사하고 메모리를 할당하는 등, 번잡한 초기화 처리가 필요하게 됩니다. 이것을 모든 클래스에서 구현하는 것은 현실적이지 않기 때문에, 이 작업을 혼자서 맡아 주는 것이 루트 클래스의 존재입니다.

Object 클래스는, Object 클래스를 포함해서 그것을 계승하는 클래스의 인스턴스를 적절히 생성하는 alloc 클래스 메소드를 정의하고 있습니다. 통상의 인스턴스 메소드는, 메소드를 호출하기 위해서 인스턴스가 필요합니다만, 클래스 메소드는 인스턴스가 없어도 실행할 수 있다고 하는 성질이 있습니다. 그 때문에, alloc 메소드는 인스턴스가 존재하지 않아도 호출하는 것에 문제는 없습니다. alloc 메소드는, 인스턴스를 생성하기 위한 클래스 메소드이므로, 흔히 팩토리 메소드라고도 불리고 있습니다.

+ alloc;

이것이,Object 클래스에서 선언되어 있는 alloc 메소드입니다. + 로 시작되고 있기 때문에, 이 메소드는 클래스 메소드인 것을 어필하고 있습니다. 반환값형은 생략 되고 있으므로, 디폴트의 id 형태가 돌려주어집니다.

id 형은, 오브젝트를 나타내는 범용형으로, 일종의 void * 와 같은 것이라고 생각해 주세요. 즉, 오브젝트의 클래스형같은것 뭐든지, 모든 오브젝트는 id 형태의 변수에 보존할 수 있습니다.

그런데, 클래스를 생성하기 위해서 alloc 메소드를 호출하지 않으면 안됩니다만, 메소드는 어떻게 호출하는 것입니까. C++ 계의 객체 지향 언어를 경험한 많은 프로그래머는, 직관적으로 다음과 같은 코드를 상상하겠지요.

id obj = 클래스명.alloc();

유감스럽지만, Objective-C에서는, 많은 객체 지향형 언어로 채용되고 있는 이 기술 방법은 틀렸습니다. Objective-C에    클래스 메소드를 호출하려면 , 다음과 같이 기술하지 않으면 안됩니다.

[클래스명 메소드명:인수 리스트...]

이것은, Smalltalk 언어를 객체 지향 부분의 어원으로 하는 Objective-C 의 특징입니다. 인스턴스 메소드를 호출하는 경우는, 클래스명의 부분에 오브젝트를 지정하지 않으면 안됩니다. C 언어에서는 [ ] 는 배열의 요소를 지정하기 위해서 사용되고 있었습니다만, 컴파일러는 [ ] 의 전후가 조사하는 것으로 메소드의 호출인가, 배열의 요소 지정인가를 판단할 수 있습니다.

이, 메소드를 호출하기 위한 [ ] 를 메세지라고 부릅니다. Objective-C는 오브젝트로부터 (함수의 주소등을 이용해) 메소드를 직접 호출하는 것이 아니라, 오브젝트에 대해서 메소드에 관련하는 메세지를 송신하고 있습니다. 메세지에 의해서 오브젝트에 메세지가 송신되면, 오브젝트는 주어진 메세지에 따라서 적절한 메소드를 기동하려고 합니다. 이러한 동적인 구조는 유연성을 확보합니다만 C 언어의 함수의 호출에 비해, 오버헤드는 배이상 걸린다고 생각해 주세요. 물론, 컴파일러가 최적화등을 실시하는 경우는 이 마지막으로는 없습니다만, 메세지에 의한 동적인 메소드의 호출은, 실행시에 메소드를 검색하기 때문에 반드시 오버헤드가 걸립니다.

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

@interface Test : Object
- (void)method;
@end

@implementation Test
- (void)method {
	printf("Kitty on your lap\n");
}
@end

int main() {
	id obj = [Test alloc];
	[obj method];
	
	return 0;
}

이것이 간단한 Objective-C 에 의한 클래스의 실현입니다. @interface 에 의한 클래스의 선언으로는, 루트 클래스 Object를 계승한 Test 클래스를 선언하고 있습니다. Test 클래스에서는, 반환값을 돌려주지 않고 인수를 받지 않는 method 메소드를 선언하고 있습니다. 그 때문에 @implementation는 이 메소드를 구현하지 않으면 안됩니다.

클래스의 선언과 정의가 종료하면, 그 후, 그 클래스를 이용할 수 있게 됩니다. main() 메소드에서는, 오브젝트를 격납하는  id 형태의 변수 obj 를 선언해, 동시에 Test 클래스의 인스턴스를 생성해 이것에 대입하고 있습니다. [Test alloc]는 Test 클래스의 클래스 메소드 alloc를 호출한다는 의미입니다. Test 클래스에서 alloc는 정의가 었었지만, alloc 메소드는 Object 루트 클래스에서 정의되고 있기 때문에, 정상적으로 동작합니다. alloc 메소드에서는,Test 클래스의 선언 정보에 근거해 적절히 메모리를 할당해 생성한 오브젝트를 돌려줍니다.

Test 클래스는 인스턴스 메소드 method를 정의하고 있기 때문에, 생성한 오브젝트의 method 메소드를 메세지로 기동할 수 있습니다. 프로그램에서는, 인스턴스 생성 직후에 [obj method] 과 같은 메세지형태로 기술하고, Test 클래스의 method 메소드를 기동하고 있습니다. 그 결과, 화면에는 Kitty on your lap 과 같은 문자열이 표시되겠지요.

덧붙여서, 오브젝트를 일회용으로 해 버린다면, 인스턴스의 생성과 method 메소드의 호출은, 다음과 같이 기술하는 것도 가능합니다.

[[Test alloc] method]

이 경우, 안쪽의 [Test alloc]가 먼저 실행되고, 생성한 인스턴스를 참조한 id 형태의 오브젝트가 돌려주어집니다. 그리고, 반환된 id 형태의 오브젝트에 대해서, 한번더 method 메세지를 송신하는 것으로, 메소드를 호출하고 있습니다. 이와 같이, 메세지는 내부에 한번더 기술할 수 있습니다. 이것은, (Test.alloc()).method() 와 같이 함수의 호출 관계에 비유할 수 있겠지요.

상기의 프로그램과 같이, 클래스의 선언과 정의의 장소는 자유롭습니다만, 습관으로서 클래스의 선언은 헤더 파일에, 정의는 클래스명과 같은 이름의 *.m 파일에 기술합니다.

원문 http://wisdom.sakura.ne.jp/programming/objc/objc3.html
번역의 귀차니즘으로 번역기 돌린 후 원문과 대조하면서 어색한 부분만 재수정한겁니다.
어휘 사용에서 다소 어색한 부분이 있더라도 양해바랍니다.

Posted by tklee

새로운 프리프로세서 명령

Objective-C 언어는 C 언어위에 확장된 언어이므로, C 언어를 그대로 사용할 수 있습니다. 보다 정확하게는, Objective-C 의 존재는 언어 사양의 확장이 아니고, 컴파일러의 확장이며, 최종적으로 컴파일러는 Objective-C 언어의 코드를 C 언어 코드로 변환해서 처리하고 있습니다. 사실, 추가된 형태나 예약어(reserved word)도, 사실상은 헤더 파일로 #define 이나 typedef 에 의해서 정의되고 있는 것에 지나지 않습니다.

거기서, Objective-C를 적절히 실행하려면 , 정해져 있는 헤더 파일을 인클루드 하지 않으면 안됩니다. Objective-C 그리고 새롭게 추가된 형태나 식별자는 objc/objc.h 헤더 파일로 정의되고 있다고 생각됩니다. 다만, 이 헤더 파일로 정의되고 있는 기능은 Objective-C 에 필요한 최소한의 것 뿐이므로, 일반적으로는 컴파일러가 제공하는 종합적인 헤더 파일을 인클루드 하는 습관이 있습니다. GCC 컴파일러에서는 objc/Object.h 헤더 파일을, Mac OS 의 Cocoa 개발 환경에서는 Foundation/NSObject.h 헤더 파일을 인클루드 하지 않으면 안됩니다.

Objective-C 컴파일러에 의존하지 않는 코드를 기술하려면 objc/objc.h 를 인클루드 합니다. 다만, 이 기능만으로는 코드가 안되기 때문에, 그곳에 objc/Object.h 를 인클루드 합니다. 왜, 확장된 헤더 파일이 필요한지에 관해서는 다음에 해설합니다.

Objective-C 에는 #inlucde 프리프로세서 지시문에 대신하는 새로운 명령 #import 프리프로세서 지시문이 추가되고 있습니다. 이 명령은 #include 와 같이 헤더 파일을 인클루드 합니다만, 한 번 인클루드 된 파일은 재 인크루드 하지 않습니다. 그 이외 대해서는 #include와 같은 기능입니다.

#import <헤더 파일>
#import "헤더 파일"

C 언어 전용의 헤더 파일을 인클루드 하는 경우는 주의하지 않으면 안됩니다만, 많은 경우는 헤더 파일을 다중에 인클루드 할 필요같은 건 없을 것입니다. 많은 헤더 파일은 다중 인클루드를 방지하기 위한 프리프로세서 명령을 지정해 있을 것이므로 #import에도  문제는 없을 것입니다.

이외에 대해서는, 종래의 C 언어와 같아서 stdio.h 헤더 파일을 인클루드해서 printf() 등의 표준 함수를 이용할 수 있습니다. 다만, Objective-C 언어의 소스 파일의 확장자(extension)는, 원칙적으로 *.m으로 정해져 있습니다.

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

int main() {
	printf("쟈. 세계시간아 멈춰!\n");
	printf("WRYYYYYYYYYYYY\n");

	return 0;
}

이 프로그램을 보고 알 수 있듯이 대부분 C 언어와 변화가 없습니다. Objective-C는 C 언어를 완전하게 계승하고 있습니다. C 언어를 닮아 있는 언어가 아니고, 완전하게 C 언어와 호환성이 있습니다.

덧붙여서,C 언어의 코멘트 /* */ 에 덧붙혀, Objective-C는 일행 코멘트 // 도 추가되고 있습니다. 이 코멘트는 C++ 언어의 코멘트와 같고,C 언어의 처리계에도 독자 사양으로서 채용하고 있는 컴파일러도 많기 때문에, 여러분 아시는 바지요.

//코멘트

Objective-C는 이 코멘트를 이용할 수 있습니다.

원문 http://wisdom.sakura.ne.jp/programming/objc/objc2.html
번역의 귀차니즘으로 번역기 돌린 후 원문과 대조하면서 어색한 부분만 재수정한겁니다.
어휘 사용에서 다소 어색한 부분이 있더라도 양해바랍니다.

Posted by tklee

객체 지향형 C언어

객체 지향형 프로그램 언어라고 하면, 많은 사람들은 C++ 이나 Java, C# 언어를 떠올리겠지요. 프로그래밍 언어계에 도통한 사람이라면, Smalltalk 라고 대답할지도 모릅니다. Objective-C도, Smalltalk와 같이 그 이상으로 광적인 객체 지향형의 프로그램 언어지요. 특히, AT호환기와 Windows 시스템을 개발하고 있는 기술자는, 이 언어에 접할 기회조차 없을지도 모릅니다.

그러나, 한편 객체 지향 언어라고 하면 Objective-C 일거라고 생각하고 있는 기술자도 존재합니다. 그 사람은 아마 Mac OS 기술자 혹은 스티브·잡스의 팬일겁니다. 현대의 컴퓨터로 가장 Objective-C 를 실제 사용하고 있는 것은 Apple 회사의 Mac OS X 입니다.

소프트웨어 개발에 Objective-C 를 실제 사용하고 있는 것은, Mac OS X 전용의 편리한 공개 소프트웨어가 인터넷으로 공개되지 않기 때문에 그럴지도 모릅니다. 왜, 이러한 마이너 언어를 이용하고 있는가 하면, Mac OS X 가 NeXT 회사의 NEXTSTEP 의 개발 언어로서 채용했던 역사적 이유에서 입니다. Mac OS X 의 GUI는,NEXTSTEP 의 API 를 계승하고 있기 때문에, 개발 언어도, 주로 Objective-C 를 이용하고 있습니다.

그러나, 두려워할 필요는 없습니다. Objective-C는 C 언어와 완전하게 호환성이 있는 언어이기에, C 언어의 원시 코드를 그대로 컴파일 할 수 있습니다. 그리고, C 언어에 객체 지향을 실현하기 위한 몇개의 기능을 추가하고 있습니다. 다행히 Objective-C 의 객체 지향은 C++ 언어보다는 심플한 구조입니다.

또, 여러가지 언어의 해설 문서를 기술해 온 필자의 경험에서는, 가장 학습 순서의 구성이 어려운 언어였습니다. Objective-C 언어의 기본 부분의 특정 기능을 학습하기 위해서는,Objective-C 언어 전체 구조를 이해해야만 하는 어려움이 있기 위해, 객체 지향 초심자가 이 언어의 기본 부분의 본질을 이해하는 것은 곤란하겠지요. 가능하다면, C++ 언어의 지식과 본질적인 객체 지향의 이해가 필요합니다.

Objective-C 와 C++

Objective-C는 C 언어의 기본 구조를 베이스로 하고,Smalltalk 언어를 도입한 언어이며, C++ 언어와는 역사적으로도 언어 사양이 완전히 다른 존재입니다. 현대의 주요한 객체 지향 언어는 그 대부분이 Java 언어를 베이스로 한 것이 많아, Smalltalk를 기본으로 하고 있는 Objective-C 의 코드를 보면, 몹시 황당해 할겁니다.

게다가 C++ 등의 다른 언어는 클래스의 실체의 메모리 구조등을 은폐 하는데 비해, Objective-C는 C 언어 를 기본으로 클래스를 실현하고 있기 때문에, 어느 정도의 구조가 C 언어로부터 볼 수 있습니다. 즉, 언어 사양으로서 새롭게 객체 지향적 성질을 더한 현대의 객체 지향형 언어에 반해, Objective-C는 C언어에 디자인 패턴으로서 객체 지향의 시스템을 추가했다고 하는 입장이라고 생각할 수 있습니다.

Java나 C++은, 클래스형의 개념이나 오브젝트의 생성 방법을 언어 사양으로서 정하고 있습니다. 그러나,Objective-C는 클래스형이나 생성 방법조차 헤더 파일로 정의되고 있기 때문에, 그 실체는 C 언어의 구조체를 typedef 문장으로 은폐 하고 있을 뿐입니다. C 언어에 친화성이 높은 것은 좋습니다만, 다른 객체 지향 언어를 습득하고 있는 기술자에게는, 받아 들이기 어려울지도 모릅니다. 반대로, C 언어프로그래밍에 객체 지향을 디자인 패턴으로서 채용하는 것을 좋아하는 개발자에게 있어서는, 추가된 언어 사양과 기반의 구조가 서포트해 주므로, 반길지도 모릅니다.

Objective-C 의 실장

Mac OS X 를 이용하고 있다면, 부속되어 있는 개발 환경을 인스톨 하는 것만으로 Objective-C 를 사용할 수 있게 됩니다. 그러나, Objective-C는 Mac OS 만의 언어는 아닙니다. 이 멋진것에 GCC 컴파일러에서도 컴파일 할 수 있으므로, Linux에서도 이 언어를 이용할 수 있습니다. Windows에도, Windows 전용 GCC 컴파일러를 인스톨 하는 것으로 사용할 수 있습니다.

원문 http://wisdom.sakura.ne.jp/programming/objc/objc1.html
번역의 귀차니즘으로 번역기 돌린 후 원문과 대조하면서 어색한 부분만 재수정한겁니다.
어휘 사용에서 다소 어색한 부분이 있더라도 양해바랍니다.
Posted by tklee