프로퍼티와 접근자(Accessor)

개발 & SDK 2009/06/10 01:16

접근자는 클래스의 멤버 변수에 접근하기 위한 함수들을 말한다. 클래스를 구현하다 보면 멤버 변수의 값을 읽고, 설정하는 작업이 매우 빈번하다. 객체지향 프로그래밍에서는 데이터 캡슐화(Encapsulation)를 위해서 클래스의 내부 변수를 직접 접근하는 대신 접근자를 이용하도록 하고 있다. 다음을 보자.

01: @interface AccessorTest : NSObject {
02:
03: BOOL enable;
04: }
05:
06: -(BOOL) enable;
07: -(void) setEnable:(BOOL)en;
08: @end

enable 이라는 멤버 변수를 접근하기 위한 접근자를 선언했다. 이와 같이 값을 읽기 위한 접근자는 그 변수 이름을 그대로 이용한다. 반대로 값을 설정하기 위한 접근자는 변수 이름의 첫글자를 대분자로 바꾸고 그 앞에 set 을 더한 이름을 사용한다. 이렇게 접근자를 선언하고 구현하면 오브젝트 C의 다른 혜택들을 자동으로 누릴 수 있다. 바로 점 표현법과 키-값 코딩이다. 다음 구문을 보자.

01: theObject.enable = YES;

위 표현이 점 표현법이다. 그런데 이 코드는 사실 다음과 같이 변환되어 처리된다.

01: [theObject setEnable:YES];

그렇다 enable 이라는 이름으로 점 표현을 사용하면 setEnable 라는 이름의 접근자를 호출하도록 미리 약속되어 있는 것이다. 이 내용은 13.3절에서 배울 키,값 코딩에도 똑같이 적용된다.

접근자를 구현한 부분을 보자.

01: @implementation AccessorTest
02:
03: -(BOOL) enable {
04: return enable;
05: }
06: -(void) setEnable:(BOOL)en {
07: enable = en;
08: }
09:
10: @end

단순하게 값을 설정하고, 현재 값을 반환하는 일이 전부다. 이처럼 접근자는 아주 편리하지만, 실상 접근자를 구현하는 작업은 거의 비슷한 코드를 반복적으로 작성하는 일이 대부분이다. 그래서 고안된 것이 프로퍼티이다. 프로퍼티는 접근자를 자동으로 생성해 준다. 프로퍼티를 정의할 때 접근자에 대한 속성을 설정해서 접근자가 구현되는 방식을 결정할 수 있다. 위 코드는 다음과 같이 대체가 가능하다.

01: @interface AccessorTest : NSObject {
02: UIImage* img;
03: NSString* text;
04: BOOL enable;
05: }
06: -(BOOL) enable;
07: -(void) setEnable:(BOOL)en;
08: @property (readwrite,assign) BOOL enable;
09:
10: @end

헤더 파일에서는 @property 구문을 선언해 준다. 그리고 구현부는 다음과 같다.

01: @implementation AccessorTest
02:
03: @synthesize enable;
04:
05: -(BOOL) enable {
06: return enable;
07: }
08: -(void) setEnable:(BOOL)en {
09: enable = en;
10: }
11: @end

접근자 구현을 전부 지우고 @synthesize 로 대체했다. synthesize 구문은 해당 프로퍼티에 대해서 접근자를 생성하도록 컴파일러에게 지시하는 역할을 한다.

* synthesize 팁

외부에서 접근하는 프로퍼티 이름과 내부의 변수 이름을 항상 일치시켜야 할까 ? 그렇지는 않다. 많은 개발자가 다음과 같은 기법을 사용한다.

01: // 헤더
02: @interface AccessorTest2 : NSObject {
03: BOOL _enable;
04: }
05: @property (readwrite,assign) BOOL enable;
06: @end
07:
08: // 구현
09: @implementation AccessorTest2
10:
11: @synthesize enable = _enable;
12:
13: @end

즉 실제 내부 변수를 언더바(_) 문자로 시작하게 선언한다. 그리고 외부에서 접근할 때는 언더바를 제거한 문자로 접근하도록 하는 것이다. 이렇게 해서 내부에서 접근할 때와 외부에서 접근할 때의 차이를 쉽게 구분하도록 한다.

self.enable = YES; // 접근자를 통한 설정
_enable = YES; // 객체 내부에서 접근

위 코드와 같이 접근자로 접근하는 것과 내부에서 접근하는 경우를 확연히 구분할 수 있다. 이 때 synthesize 에 = 가 사용된 것은 enable 이라는 프로퍼티가 _enable 이라는 멤버 변수를 사용한다는 것을 알리기 위한것이다.

* 프로퍼티 선언

다음으로 @property 에 속성을 설정하는 방법을 알아보자. 코드의 5행을 보면 @property 구문에 readwrite, assign 이라는 속성을 별도로 적용한 것을 볼 수 있다. 이러한 속성을 기술하는 이유는 컴파일러가 접근자를 자동으로 생성할 때 세부 동작 방식을 결정하기 위해서 이다. 예를 들어가며 하나씩 알아보자.

01: @property (getter = isEnable, setter = setEnable) BOOL enable;

이 코드는 접근자의 메서드 명을 직접 설정할 때 사용한다. 컴파일러는 @synthesize 로 선언한 프로퍼티의 접근자들(getter/setter) 중에서 직접 구현되지 않은 것들을 직접 생성하는데, 이 때 getter 와 setter 이름을 위와 같이 지정해 놓으면 해당 이름을 갖는 접근자를 생성한다. 하지만 특별한 경우를 제외하고는 바꾸지 않도록 하자. 대부분의 경우 접근하려는 변수가 BOOL 형식일때 isProperty 라는 이름으로 getter 를 변경할때 만 사용된다.

01: @property (readonly, assign) BOOL enable;

이 코드는 enable 프로퍼티를 오직 읽기만 가능하도록 설정한다. 즉 setter 메서드를 생성하지 않는다. 그래서 만약 self.enable = YES; 와 같이 설정하려고 하면 컴파일 에러를 발생시킨다. readonly 를 따로 설정하지 않으면 readwrite 를 설정한 것과 같다. readwrite 속성은 따로 기술하지 않아도 기본값으로 적용이 된다.

01: @property (nonatomic, retain) UIImage* img;
02: @property (nonatomic, copy) NSString* name;
03: @property (nonatomic, assign) BOOL enable;

위 3가지 사용법을 보자. 먼저 nonatomic은 다중 쓰레드에 대해서 고려할지를 선택하기 위한 것이다. 만약 다중 쓰레드 환경에서 여러 쓰레드가 경쟁적으로 접근하는 프로퍼티가 있다면 atomic 으로 설정해야 한다. 하지만 그외의 대부분의 경우 nonatomic 을 사용한다.

다음으로 가장 중요하고 또 가장 햇갈리기도 한 retain, assign, copy 를 보자. 이 3가지 속성은 각각 설정자(setter) 의 동작을 결정한다. 각각을 정리해 보자.

* retain

01: @property (nonatomic, retain) UIImage* img;

위 코드는 retain으로 설정되어 있다. 그래서 컴파일러는 다음과 같은 코드를 만들어 낸다.

01: -(void) setImg:(UIImage*) newImg {
02: if (img != newImg ) {
03: [img release];
04: img = [newImg retain];
05: }
06: }

4행이 핵심이다. 즉 img 에 새로운 객체를 설정하면, 그 새로운 객체에 retain 을 한번 호출해 준다. 이는 외부에서 그 객체를 release 하더라도 객체가 메모리 해제되지 않도록 유지하기 위한 것이다. 당연히 retain 속성은 Objective-C 객체만을 대상으로 해야한다.

* copy

@property (nonatomic, copy) NSString* name;

위 코드는 copy 속성을 이용한다. 컴파일러가 만들어 내는 코드는 다음과 같다.

01: -(void) setName:(NSString*) newName {
02: if (name != newName ) {
03: [name release];
04: name = [newName copy];
05: }
06: }

copy 의 경우는 4행에서 처럼 설정하려는 객체의 값을 복사해서 자신이 가지고 있도록 한다. copy 메서드는 기본적으로 retain 을 한번 호출하기 때문에 따로 retain 을 추가로 호출하지는 않는다. copy 는 값을 복사해서 새로운 객체를 만들기 때문에 인자로 전달되는 객체가 자주 변경될 소지가 있는 경우에 적합하다.

copy 의 경우는 Objective-C 객체 중에서도 NSCopying 프로토콜을 따르는 것들만 적용이 가능하다는 점을 주의하자.

* assign

@property (nonatomic, assign) BOOL enable;

assign 은 가장 단순한 방법으로 그냥 값을 대입시킨다.

-(void) setEnable:(BOOL) en {
enable = en;
}

Objective-C 객체를 assign 으로 설정하는 경우는 외부에서 참조 카운트를 감소시켜 객체가 해제될 위험이 있기 때문에 주의해서 사용해야 한다. 그렇기 때문에 assign은 객체가 아닌 BOOL, int 등의 일반 값에 대해서 적합하다.

* 메모리 해제

앞에서 설명한 copy, retain 으로 지정한 프로퍼티들은 그 객체가 해제될때 같이 해제하는 작업이 필요하다. 당연히 copy 와 retain 은 객체에 대해서 retain 을 호출해서 참조 카운트를 증가시키기 때문에, 객체가 해제될 때 같이 release 메서드로 해제해야 한다.

01: -(void) dealloc {
02: [name release];
03: [img release];
04: [super dealloc];
05: }

Creative Commons License
Trackback 0 : Comment 0

Trackback Address :: http://maclove.pe.kr/trackback/28 관련글 쓰기

Write a comment

◀ PREV : [1] : ... [7] : [8] : [9] : [10] : [11] : [12] : [13] : [14] : [15] : ... [30] : NEXT ▶