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

프로토콜이라고 하는 용어는, 비교적 네트워크용어로서 이용되고 있습니다만, 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

댓글을 달아 주세요