메서드를 클래스에서 공유
프로토콜이란 대부분 네트워크 용어로 사용되고 있지만, 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) return ToString (id) name;}
@end
@implementation B
- (id) {return ToString (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 <InstanceListener>
{
Id 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를 동시에 사용하고 있는 점도 주목하십시오. 선언으로만 되어있는 프로토콜에서 메서드 이름이 충돌해도 해당 프로그램의 메소드 검색에는 아무런 영향을 주지 않기 때문에 가능한 것입니다. 그러나 메소드가 프로토콜의 상속 관계에서 충돌할 경우에는 조심해야 할 필요가 있을 것입니다.