Objective_C 2020. 4. 14. 18:50

7.프로토콜

특정 객체에 변화가 일어나면 혹은 어떤 특별한 의미를 가지는 시점에 도달하면 해당 객체로부터 이에 대한 알림 서비스를 받고 싶을경우 '프로토콜'를 사용한다.

프로토콜을 사용하면 객체로부터 콜백 메소드의 형태로 알림 서비스를 받을 수 있다.

1.프로토콜을 선언하는 brush 클래스가 구현되는 방식

//헤더파일

@protocol 프로토콜 이름;            //(1) 프로토콜을 선언하는 부분

 

@interface 클래스 이름: 부모 클래스 이름{

    id<프로토콜 이름> delegate;    //(2)프로토콜을 사용하고자 하는 클래스 인스턴스의 포인터

    …

}

@property (nonatomic, retain) id<프로토콜 이름> delegate;

//프로토콜을 사용하는 메소드 //(3) 프로토콜을 사용하는 메소드를 선언한다.

@end

 

@protocol 프로토콜 이름 <타 프로토콜 이름> //(4)@protocol에서 @end까지 프로토콜을 정의하는 부분

@required                //(5)required에는 반드시 정의해야하는 메소드(기본)

메소드 선언 리스트

@optional                //(5)optional에는 반드시 정의할 필요는 없는 메소드를 선언

메소드 선언 리스트

@end

 

//소스파일

#import "헤더 파일"

@implementation

@synthesize delegate;

...

프로토콜에서 선언된 메소드를 호출하는 메소드 정의 //(6) 프로토콜에서 선언된 메소드를 정의한다.

@end

(2)프로토콜을 사용하고자 하는 클래스 인스턴스의 포인터를 저장하는 부분, 이 값을 정의하지 않으면 프로토콜 메소드를 호출할 방법이 없으므로 프로토콜을 사용하는 클래스 인스턴스에서 반드시 설정해야한다.

 

(3) 프로토콜을 사용하는 메소드를 선언한다. 당연한 이야기지만 어느 메소드가 프로토콜 내의 어느 메소드를 호출해야 한다는 내용은 정해져 있지 않다. 인터페이스 내의 어떠한 메소드라도 프로토콜 내에 선언되어 있는 메소드를 호출할 수 있다. 즉, 적절한 메소드에서 필요한 시점에 호출해주면되는 것이다.

 

(4)@protocol에서 @end까지 프로토콜을 정의하는 부분.'<타 프로토콜 이름>'을 지정하게 되면 해당 프로토콜의 내용을 가져와 현재 프로토콜에 포함한다.

 

ex)프로토콜을 선언하는 brush 클래스가 구현되는 방식

//Brush.h

#import <Foundation/Foundation.h>

@protocol BrushDelegate;    //(1)

@interface Brush : NSObject {

id<BrushDelegate> deleage; //(2)

}

@property (nonatomic, retain) id<BrushDelegate> delegate;

-(void)changeColor;    //(3)

@end

 

@protocol BrushDelegate<NSObject> //(4)

@optional             //(5)

-(void)brush(Brush *)brush WillStartChangingColor:(UIColor *)color;

-(void)brush(Brush *)brush didStartChangingColor:(UIColor *)color;

-(void)brush(Brush *)brush willFinishChangingColor:(UIColor *)color;

-(void)brush(Brush *)brush didFinishChangingColor:(UIColor *)color;

@end

 

//Brush.m

#import "Brush.h"

@implementation Brush

@synthesize delegate;

-(void)ChangeColor {            //(6)

    UIColor *color = [UIColor blackColor];

    if([self.delegate respondsToSelector:@selector(brush:willStartChangingColor;)]) {

     [self.delegate brush:self WillStartChangingColor:color];

    }

…..

}

@end

respondsToSelector 는 메소드를 호출하는 것에 주목하자. 이 메소드는 NSObject에 선언된 것인데, 호출하고 있는 객체 내에 어떠한 메소드가 정의되어 있는지 검사한다.

 

2. 프로토콜(BrushDelegate )을 사용하고자 하는 클래스의 구조

//헤더 파일

#import "프로토콜을 선언한 헤더파일"    //(1)프로토콜을 선언한 헤더 파일을 추가

@interface 클래스이름 : 부모클래스이름 <사용하려고하는 프로토콜이름>{ //(2) 사용하려는 프로토콜이름 명시

프로토콜을 선언하고 있는 클래스 타입     인스턴스 변수 //(3)프로토콜클래스의 인스턴스변수 선언

}

@end

//소스파일

#import "헤더 파일"

@implementation

@synthesize (3)에서 선언된 인스턴스 변수

….

인스턴스 변수(3)에게 프로토콜을 사용하고자 하는 현재 클래스 인스턴스의 주소값을 넘겨줌 //(4)

프로토콜 내에 선언된 메소드 정의 //(5)

@end

(4)현재 클래스 인스턴스의 주소값을 넘겨줌(5)에서 정의하고 있는 메소드가 정상적으로 호출되도록 한다.

(5) 프로토콜을 선언하여 정의하고 있는 클래스에서는 메소드를 선언만 하고 있을 뿐 구현은 하지 않고 있다. 이는 전적으로 프로토콜을 사용하는 클래스의 몫으로 남겨져 있다. 그러므로 해당 메소드에 대한 구현 부분이 반드시 이곳을 존배해야한다.

 

ex)BrushDelegate를 사용한 Rectangle 클래스

//Rectangle.h

#import <Foundation/Foundation.h>

#import "Shape.h"

#import "Brush.h"

 

@class Circle;

@interface Rectangle : shape <BrushDelegate> {

@protected

Brush *brush;

NSString *name;

NSInteget width;

NSInteget height;

}

@property (nonatomic, retain)Brush *brush;

@property (nonatomic, retain)NSString *name;

@property (nonatomic, retain)NSInteger width;

@property (nonatomic, retain)NSInteger height;;

 

-(id) initWithWidth:(NSInteger)myWidth height:(NSInteger)myHeight;

-(NSString *)description;

-(void)draw;

@end

 

//Rectangle.m

@import "Rectangle.h"

@implementation Rectangle

@synthesize brush;

@synthesize name;

@synthesize width;

@synthesize height;

-(id)init{

return [self initWithWidth:10 height:20];

}

-(id) initWihWidth:(NSInteger)myWidth height:(NSInteger)myHeight{

if(self =[super init]){

brush =[[Brush alloc] init];

brush.delegate = self;

name = nil;

width = myWidth;

height = myHeight;

}

return self;

}

-(void)brush(Brush *)brush WillStartChangingColor:(UIColor *)color

{…}

-(void)brush(Brush *)brush didStartChangingColor:(UIColor *)color

{…}

-(void)brush(Brush *)brush willFinishChangingColor:(UIColor *)color

{…}

-(void)brush(Brush *)brush didFinishChangingColor:(UIColor *)color

{…}

-(NSString *)description{

NSString *text =nil;

Text = [[NSString alloc] initWithString:@"Test";

[text autorelease];

return text;

}

-(void)draw{

[super draw];

}

-(void)dealloc{

[name release];

[super dealloc];

}

@end

프로토콜 내에는 하나 또는 그 이상의 메소드를 선언할 수 있으며, 이들은 이후 이 프로토콜을 사용하고자 하는 클래스의 소스파일에서 정의한다. 그리고 이 프로토콜을 이용하는 클래스가 있어야한다. 당연한 이야기이겠지만 프로토콜을 이용하는 클래스의 수와 종류에는 제한이 없다. 어느 클래스라도 프로토콜을 선언하고 있는 클래스를 자신의 멤버 변수로 포함하고 이용하면 되는 것이다.

실제로 파운데이션 프레임워크 내의 많은 클래스도 이처럼 프로토콜을 이용할 수 있도록 구현되어 있다.

다양한 프로토콜 메소드를 이용하는 대표적인 예로 테이블뷰(UITableView)를 들수 있다.

8.카테고리와 클래스 확장

카테고리는 특정 클래스의 소스를 가지고 있지 않더라도 해당 클래스에 새로운 메소드를 추가하는 것을 상속 없이 가능케한다.

 

8-1클래스에 메소드 추가하기

//헤더파일

#import "클래스 이름" //(1)카테고리를 이용해서 메소드를 추가하고자 하는 클래스의 헤더 파일을 추가한다.

@interface 클래스 이름 (카테고리 이름) //(2)클래스 이름 괄호안에 카테고리 이름을 지정

//메소드 선언 //(3)클래스에 추가하는 메소드를 선언 인스턴스변수는 추가할 수 없다.

@end

//소스 파일

#import "헤더 파일" //(4)헤더 파일을 추가한다. 일반적으로 파일의 이름은"클래스이름+카테고리이름'으로 지정

@implementation 클래스 이름 (카테고리 이름)

//메소드 정의 //(5) 클래스에 추가 선언한 메소드를 정의한다.

@end

ex)카테고리 예

//Rectangle+Display.h

#import <Foundation/Foundation.h>

#import "Rectangle.h"

@interface Rectangle (Display)

-(void)displayDetail;

@end

 

//Rectangle+Display.m

#import "Rectangle+Display.h"

@implementation Rectangle(Display)

-(void)displayDetail{

}

@end

8-2 카테고리의 장점과 단점

-장점

*유사한 메소드를 한곳으로 모아 관리할수 있다.

*여러 개발자가 동시에 하나의 큰 클래스를 설계하고 구현할 때 카테고리로 나눠 작업 할 수 있다.

*큰 클래스를 여러 카테고리로 분리해놓으면 컴파일 할 때 전체를 빌드하지 않고 일부만 컴파일 할수 있다.

-단점

*부모 클래스의 메소드를 재정의(오버라이딩)하고 있는 클래스 메소드를 카테고리에서 또다시 재정의하면 문제가 발생한다. 카테고리에서 부모 클래스의 메소드는 super 지시어를 이용하여 접근할 수 있으나, 클래스에서 이전에 재정의한 메소드는 호출할 방법이 없기 때문이다.

*클래스의 메소드는 둘 이상의 카테고리에서 재정의하면 문제가 발생한다. 두 메소드중 어느 것이 우선권을 가지는지 알수 없기때문이다.

*기존 메소드를 재정의 하는 것 이외에도 문제가 생길 수 있다.

 

8-3.루트 클래스의 카테고리

루트 클래스인 NSObject에 카테고리를 이용하여 메소드를 추가하면 프로젝트의 모든 코드에서 이를 사용할 수 있게 되므로 매우 위험하다. 또한 루트 클래스에는 부모 클래스가 없으므로 루트 클래스에 메소드를 추가할 때 super지시어를 사용해서는 안된다.

그리고 루트 클래스는 다른 클래스와 달리 클래스 객체(인스턴스 변수가 아닌 클래스 이름 자체를 사용하는 경우)에서도 클래스 메소드 뿐 아니라 인스턴스 메소드를 호출할 수 있다는 사실을 기억해두자

 

8-4.클래스 확장

클래스 확장은 카테고리와 거의 유사하다. 다른 점은 카테고리 이름을 사용하지 않으며, 메소드를 구현하는 부분이 확장하려는 클래스의 구현 부분에 반드시 존재해야한다는 것이다.

ex)

//Rectangle.h

#import <Foundation/Foundation.h>

#import "Shape.h"

#import "Brush.h"

 

@class Circle;

@interface Rectangle :Shape <BrushDelegate>{

@protected

Brush *brush;

NSString *name;

NSInteger width;

NSInteger height;

}

@property (nonatomic, retain) Brush *brush;

@property (nonatomic, retain) NSString *name;

@property (nonatomic, retain) NSInteger width;

@property (nonatomic, retain) NSInteger height;

 

-(id) initWithWidth:(NSInteger)myWidth height:(NSInteger)myHeight;

-(NSString *)description;

-(void)draw;

@end

@interface Rectangle()

-(void)displayDetailEx;

@end

//Rectangle.m

#import "Rectangle.h"

@implementation Rectangle

 

@synthesize brush;

@synthesize name;

@synthesize width;

@synthesize height;

…..

-(void)displayDetailEx{

..

}

@end

'Objective_C' 카테고리의 다른 글

[Objective-C] 기초배우기 (4)  (0) 2020.04.14
[Objective-C] 기초배우기 (2)  (0) 2020.04.14
[Obejective-C] 기초배우기 (1)  (0) 2020.04.13
posted by 핵커 커뮤니티
: