objective c 프로토콜 1
이전 포스팅에서 알아본 카테고리와 가상 클래스, 가상 메소드는 한계가 있다. 즉, 카테고리와 가상 클래스는 선언되고 구현된 어떤 특정 클래스에만 영향을 미친다는 것이다. 카테고리는 특정한 클래스에 메소드를 추가하는 방법이고, 가상클래스는 objective c의 다형성을 이용하여 가상메소드로 전달된 명령을 각 서브클래스가 자신의 특성에 맞게 상세 명령으로 수행하는 방법이다.
위의 카테고리는 현재 클래스를 수평적으로 확장하는 역할을 하고, 가상함수는 현재 클래스를 수직적으로 확장하는 역할을 한다.
메소드 확장을 다중상속은?
메소드 확장을 통한 다중상속 개념을 구현하기 위해서는 서로 다른 클래스에 동일하게 적용할 수 있는 메소드 확장클래스 기법이 필요하다. 이를 위해 objc 에서는 메소드를 추가한다는 의미에서 카테고리와 비슷하지만 여러 클래스에 적용할 수 있는 프로토콜이라는 방법을 제공한다.
objc 에서 프로토콜을 간단히 정의하면 단순 메소드 선언의 묶음이다. 이런 이유로 프로토콜 단계에서는 .h 헤더 파일만 존재하고, .m구현 파일은 존재하지 않는다. 메소드 묶음으로 선언된 프로토콜은 이를 사용할 클래스에서 꺽쇠 <, >로 상속을 표현하고 #import하게 되면 프로토콜이 채용되면서 각 메소드를 정의할 수 있다.
프로토콜 <Print>를 채용하는 클래스는 이 프로토콜이 선언한 메소드를 모두 구현하여야한다. 이렇게 프로토콜을 채용한 클래스는 이 프로토콜의 기능이 필요한 다른 클래스의 요청에 응답할 수 있게 된다. 완전한 다중상속은 아니지만 공통된 작업을 수행한다는 의미에서 다중상속과 유사하게 활용할 수 있다.
아래에서 모든 클래스의 조상 NSObject를 직접 또는 간접적으로 상속받은 어떤 클래스가 있을 때 프로토콜은 따른다는것은 다중상속과 비슷하게 동작한다. 물론 프로토콜은 카테고리와 마찬가지로 자체 멤버변수를 가지지 못한다. 그러나 이런 불편을 감수하고 프로토콜 방식으로 다중상속을 구현하는 것이 현재 객체지향언어의 대세이다.
그림을 보면 프로토콜은 다양한 클래스에 적용가능한 카테고리와 같으며 외형은 가상함수의 묶음으로 표현되기 때문에 카테고리와 가상함수를 합친 것과 같아 보인다. 그리고 후에 나올 비공식 프로토콜은 실제 카테고리로 구현된다. 카테고리와 프로토콜, 가상함수의 구현은 표현법만 다를 뿐 X윈도우의 콜백 함수와 그 내부 구현 방법이 크게 다르지 않다.
프로토콜의 선언과 클래스 @interface에서 프로토콜 채용하는 방법
프로토콜 예제
Print.h
@protocol Print <NSObject> // Protocol inheritance
//가상메소드 선언 - 프로토콜에는 메소드 정의가 없다.
-(NSString *)methodA;
-(NSString *)methodB;
-(NSString *)undefined;
@end
ClassWithoutProtocol.h
#import <Foundation/Foundation.h>
@interface ClassWithoutProtocol : NSObject
//프로토콜을 채용하지 않았지만
//프로토콜에서 선언한 모든 메소드와 같은 이름의 메소드를 구현한 클래스 선언/정의
-(NSString *)methodA;
-(NSString *)methodB;
-(NSString *)undefined;
@end
ClassWithoutProtocol.m
#import "ClassWithoutProtocol.h"
@implementation ClassWithoutProtocol
-(NSString *)methodA
{
return @"methodA from Without Protocol";
}
-(NSString *)methodB
{
return @"methodB from Without Protocol";
}
-(NSString *)undefined
{
return nil;
}
@end
Ancestor.h
#import <Foundation/Foundation.h>
@interface Ancestor : NSObject
//프로토콜을 채용하지 않고,
//프로토콜에서 선언한 일부 메소드를 구현한 클래스 선언/정의
-(NSString *)methodA;
@end
Ancestor.m
#import "Ancestor.h"
@implementation Ancestor
-(NSString *)methodA
{
return @"methodA from ancestor.(프로토콜 메소드 x)";
}
@end
ClassWithProtocol.h
#import <Foundation/Foundation.h>
#import "Print.h"
#import "Ancestor.h"
//상위클래스 Ancestor 상속
//프로토콜 Print 상속
@interface ClassWithProtocol : Ancestor <Print>
@end
ClassWithProtocol.m
#import "ClassWithProtocol.h"
@implementation ClassWithProtocol
// -methodA 는 Ancestor에서 상속,
// -methodB는 자체 정의한다.
// -undefined는 정의하지 않아 경고 발생.
-(NSString *)methodB
{
return @"methodB from Protocol.";
}
// Warning : method 'undefined' in protocol 'Print' not implemented
@end
main.m
#import <Foundation/Foundation.h>
#import "ClassWithoutProtocol.h"
#import "ClassWithProtocol.h"
int main(int argc, const char * argv[])
{
@autoreleasepool
{
id a, b, c;
a = [[ClassWithoutProtocol alloc] init];
b = [[Ancestor alloc] init];
c = [[ClassWithProtocol alloc] init];
//프로토콜을 채용하는지 채크!
//ClassWithProtocol 만 프로토콜을 채택했다.
printf("%d %d %d\n", // 0 0 1
(int)[a conformsToProtocol:@protocol(Print)],
(int)[b conformsToProtocol:@protocol(Print)],
(int)[c conformsToProtocol:@protocol(Print)]);
//인스턴스 c가 프로토콜의 각 메소드에 응답하는지 여부 체크
//methodA는 부모 클래스 Ancestor에서 구현
//methodB는 ClassWithProtocol에서 구현
//undefined 메소드는 채용만했지 구현은 안해주었다. 그래서 0
printf("%d %d %d\n", // 1 1 0
(int)[c respondsToSelector:@selector(methodA)],
(int)[c respondsToSelector:@selector(methodB)],
(int)[c respondsToSelector:@selector(undefined)]);
printf("%s \n%s\n",
[[c methodA] UTF8String],
[[c methodB] UTF8String]);
//오류. 발생.
//printf("%s\n", // Thread 1: signal SIGABRT
// [[c undefined] UTF8String]);
[a release];
[b release];
[c release];
}
return 0;
}
결과
0 0 1
1 1 0
methodA from ancestor.(프로토콜 메소드 x)
methodB from Protocol.
Program ended with exit code: 0
위의 그림에서 ClassWithoutProtocol 클래스는 프로토콜이 <Print>에서 필요로 하는 모든 메소드를 가지고 있지만 -conformsToProtocol:의 반환결과는 0 으로 나왔다. 이와 대조적으로 ClassWithProtocol 클래스는 -undefined 메소드가 프로토콜에서 선언만 되어 있고 정의를 가지고 있지 않음에도 -confirmsToProtocol:의 반환 결과는 1 로 나왔다.
프로토콜의 채용과 따름의 용어적 혼돈
프로토콜 관련 문서에 따르면 클래스 언어에서 프로토콜이 < , > 로 지정되면 프로토콜을 채용한 것이며, 채용된 프로토콜의 메소드가 클래스의 구현 (m) 파일에서 모두 정의되었으면 프로토콜을 따른다고 표현한다. 그러나 실제 다음 메소드는 adop와 confirm의 정의와 조금 다르게 동작한다.
+(BOOL) conformsToProtoco:(Protocol *)aProtocol
-(BOOL) conformsToProtoco:(Protocol *)aProtocol
위의 예제에서 클래스 ClassWithProtocol은 메소드 -undefined를 정의하지 않고 있으므로 프로토콜 <Print>를 완전히 따르고 있지는 않다. 그러나 위의 메소드로 확인결과 참을 반환한다.
반면 클래스 ClassWithoutProtocol은 프로토콜 <Print>의 메소드를 모두 구현했지만, 거짓을 반환한다. 이는 실제 confirm의 의미보다 프로토콜의 adopt 여부에 의해 그 반환값이 결정되기 때문이다.
정리
1. 프로토콜의 채용 여부는 프로토콜의 실제 메소드를 모두 구현하였는지 여부가 아니라 클래스의 선언에서 프로토콜을 상속 받았는지 여부에 의해 결정된다. 반대로 메소드를 모두 구현했다고 해도 프로토콜을 상속받지 않았다면 프로토콜을 따른다고 할 수 없다.
2.프로토콜의 메소드 구현은 상위 클래스에서 구현되어 있어도, 상속을 통해 활용할 수 있다. 즉, 채용된 클래스에서 프로토콜 메소드를 모두 정의 또는 재정의할 필요는 없다. 그러나 상위 클래스에서 정의된 메소드가 다른 프로토콜에 포함되어 있을 경우 혼동이 유발된다.
3.프로토콜을 채용하였으나, 구현되어 있지 않다면, 컴파일 경고가 발생한다. 즉, 일반 클래스에서 선언만 있는 메소드에서 발생하는 경고와 동일한 결과가 나타난다.
4.구현되지 않은 프로토콜 메소드를 호출하면 SIGABRT 시그널이 발생하고, 프로그램은 중단된다. 프로토콜의 구현되지 않은 메소드에 대해서는 컴파일 경고가 발생하기 때문에 사전에 확인은 가능하다. 하지만 비공식 프로토콜의 경우 미정의되어도 컴파일 경고가 발생하지 않는다.
'아이폰 개발 > Objective C' 카테고리의 다른 글
objective c 프로토콜 3 - 비공식 프로토콜 (0) | 2020.12.31 |
---|---|
objective c 프로토콜 2 - 프로토콜의 채용, 상속 카테고리화 (0) | 2020.12.31 |
objective c 클래스 클러스터의 서브 클래스 (0) | 2020.12.30 |
objective c 클래스 클러스터(추상클래스) (0) | 2020.12.29 |
objective c 카테고리 (0) | 2020.12.29 |