Property Declaration and Implementation
OSXDEV
프라퍼티에는 두개의 파트가 있다. 바로 선언과 그 구현이다.
목차 |
[편집] Property Declaration
프라퍼티 정의는 @property 키워드로 시작한다. @property는 클래스의 @interface 메소드 선언 리스트 어디에서든 나타날 수 있다. @property는 프로토콜이나 카테고리 선언에서도 나타날 수 있다.
@property(attributes) type name:
@property는 프라퍼티를 정의한다. 괄호쳐진 부가적 어트리뷰트 세트는 저장 방식과 프라퍼티의 다른 비헤비어들에 대한 자세한 부분을 제공한다 - 사용가능한 값은 "Property Declaration Attribute" 를 보라. 다른 Objective-C 타입과 마찬가지로, 각각의 프라퍼티는 타입 명세와 이름을 가지고 있다.
[편집] Property Implementation Directives
특정 컴파일 액션을 시동하기 위해 @implementation 블럭 안에서 @synthesize와 @dynamic 지시어를 사용할 수 있다. @property 정의를 위해 반드시 필요한 것은 아니다.
|
중요: 기본값은 @dynamic이다. 만약 특정 프라퍼티에 대해 @synthesize나 @dynamic중 어떤 것도 지정하지 않았다면, 해당 프라퍼티에 대한 getter와 setter 메소드 구현을(readonly 프라퍼티의 경우 getter만) 제공해야만 한다 |
@synthesize
@synthesize키워드를 이용해 컴파일러에게 @implementation 블럭 내에서 직접 제공하지 않는다면 프라퍼티에 대한 setter/getter 메소드 둘다 혹은 하나를 만들어 내도록 할 수 있다.
Listing 4-2 @synthesize 사용하기
@interface MyClass : NSObject
{
NSString *value;
}
@property(copy, readwrite) NSString *value;
@end
// assume using garbage collection
@implementation MyClass
@synthesize value;
@end
특정 인스턴스 변수가 그 프라퍼티를 위해 사용되어야 한다는 것을 가리키기 위해 property=ivar의 형식을 이용할 수 있다. 예를 들면:
@synthesize firstName, lastName, age = yearsOld;
이것은 firstName, lastname 그리고 age의 엑세서 메소드가 생성되어야 한다는 것을 지정하며 프라퍼티 age는 인스턴스 변수 yearsOld를 나타낸다. 생성된 메소드의 다른 요소들은 부가적인 어트리뷰트에 의해 결정된다 ("Property Declaration Attributes"를 보라).
런타임에 따라 비헤비어에 차이점이 있다 ("Runtime Differences"를 보라):
- "fragile 인스턴스 변수"(32-bit) 런타임에서, 인스턴스 변수들은 @interface 블럭에 이미 정의되어 있어야 한다. 프라퍼티와 같은 이름과 호환가능한 타입의 인스턴스 변수가 있다면, 그것이 사용된다 - 혹은 컴파일 에러가 생긴다.
- "non-fragile 인스턴스 변수"(64-bit) 런타임에서, 인스턴스 변수들은 필요에 따라 생성된다. 만약 같은 이름의 인스턴스 변수가 이미 있다면, 그것이 사용된다.
@dynamic
@dynamic 키워드를 이용해 컴파일러에게 프라퍼티에 함축된 API 계약사항을 이행하는 메소드 구현을, 직접 제공하거나 런타임에서 코드의 다이나믹 로딩 또는 다이나믹 메소드 resolution 같은 다른 메커니즘을 이용하여 제공할 것임을 알린다. Listing 4-3의 예제는 직접 메소드 구현방식을 이용한다 - 이것은 Listing 4-2에서 주어진 예제와 동일하다
Listing 4-3 @dynamic과 직접 메소드 구현방식을 사용하기
@interface MyClass : NSObject
{
NSString *value;
}
@property(copy, readwrite) NSString *value;
@end
// assume using garbage collection
@implementation MyClass
@dynamic value;
- (NSString *)value {
return value;
}
- (void)setValue:(NSString *)newValue {
if (newValue != value) {
value = [newValue copy];
}
}
@end
[편집] Property Declaration Attributes
@property(attribute [, attribute2, ...])의 형식으로 어트리뷰트를 이용해서 프라퍼티를 꾸밀 수 있다. 메소드처럼, 프라퍼티는 그 내장된 인터페이스 선언을 유효범위로 한다. 콤마로 분리된 변수 이름을 콤마로 분리하는 프라퍼티를 선언의 경우, 프라퍼티 어트리뷰트들은 모든 named 프라퍼티에 적용된다. 가베지 컬렉션을 사용한다면, storage modifier인 __weak와 __strong을 프라퍼티 선언에 사용할 수 있지만, 이는 어트리뷰트 리스트의 정형화된 부분은 아니다.
getter=getterName, setter=setterName
getter=과 setter=는 각각 프라퍼티의 get과 set 엑세서의 이름을 지정한다. 이름의 기본값은 키-밸류 코딩의 규약을 따른다 (Key-Value Coding Programming Guide를 보라).
getter는 프라퍼티의 타입에 대응하는 타입을 리턴해야 하고 매개변수를 받지 않는다. setter 메소드는 프라퍼티 타입에 대응하는 하나의 매개변수를 받아야 하며 void를 리턴해야 한다.
readonly
프라퍼티가 읽기 전용임을 가리킨다. 기본값은 읽기/쓰기 이다.
값을 지정하려 한다면(dot 구문을 이용해서도) 컴파일러 에러가 난다. @implement안에서는 getter 메소드만 있으면 된다. 혹은 @synthesize를 사용하면 getter 메소드만 생성된다.
readwrite
프라퍼티는 읽기/쓰기로 처리되는 것을 나타낸다. 이 값이 기본값이다.
getter와 setter 메소드 모두가 @implementation에 필요하다. 혹은 @synthesize를 사용하여 getter와 setter메소드가 생성되도록 한다.
assign
setter가 단순한 assignment 사용한다고 지정. 이 값이 기본값이다.
당신의 어플리케이션이 가베지 컬렉션을 사용한다면, NSCopying 프로토콜을 적용하는 클래스의 프라퍼티에 assign을 사용하고 싶다면, 그냥 기본값에 의존하지 말고 어트리뷰트를 명백하게 지정해야 한다 - 아니면 컴파일러 경고를 받게 된다. (이것은 그 값이 copy가능한 값이라 해도 assign하길 원한다는 것을 확실히 함으로서 컴파일러를 안심시키기 위한 것이다.)
retain
assignment 시에 오브젝트에 대해 retain이 발생해야 한다는 것을 지정 ( 기본값은 assign.)
이 어트리뷰트는 Objective-C 오브젝트 타입에만 유효하다. (Core Foundation 오브젝트에 retain을 지정할 수 없다 - "Core Foundation"을 보라.)
copy
assignment시에 오브젝트의 copy가 사용된다는 것을 지정. (기본값은 assign.)
복사는 copy 메소드를 발생시켜서 일어난다. 이 어트리뷰트는 NSCopying 프로토콜을 구현하는 오브젝트 타입에 대해서만 유효하다.
nonatomic
엑세서가 non-atomic임을 지정한다. 기본값은, 엑세서는 actomic하다는 값이다.
기본적으로, 엑세서는 atomic이다. 이것은 기본적으로 생성된 엑세서들은 멀티- 스레드 환경에서 프라퍼티에 대해 robust 엑세스를 제공한다는 의미이다 - 즉, getter로부터 리턴되는 값이나 setter에 의해 설정되는 값들은 항상 다른 쓰레드가 동시에 실행중인 것에 상관없이 전체적으로 완전히 가져오거나 지정된다. 보다 자세한 사항은 "Preformance and Threading"을 보라
컴파일러가 엑세서 메소드를 만들도록 @synthesize 지시어를 사용하면, 만들어내는 코드는 키워드 지정에 따른다. 엑세서 메소드를 직접 구현한다면, 지정한 것에 맞춰줘야 한다 (예를들어 copy를 지정했다면 setter메소드에서 입력값을 복사해 줘야 한다).
클래스, 카테고리 또는 포로토콜 선언시 getter=, setter=, readonly, readwrite 인터페이스 어트리뷰트를 사용할 수 있다.어트리뷰트 리스트에서는 readonly와 readwrite중 하나만을 사용할 수 있다. setter=/getter=는 둘다 부가적인 것이며 readonly에서 다른 어트리뷰트들의 저장을 위해 나타날 수 있다. 프라퍼티를 readonly로 지정하고 setter=로 setter를 지정한다면, 컴파일러가 경고를 보낼 것이다.
assign, retain과 copy는 상호 배타적이다. 차이점은 가베지 컬렉션을 사용하느냐 하지 않느냐에 따라 달라진다.
- 가베지 컬렉션을 사용하지 않는다면, 오브젝트 프라퍼티에 대해 assign, retain 또는 copy중 명백하게 하나를 지정해야 한다 - 그렇지 않으면 컴파일러가 경고를 보낼 것이다. (이것은 당신에게 어떤 메모리 관리 비헤비어를 사용할 것인지 고민하도록 하고 명백하게 그 타입을 지정하게 한다.)
- 가베지 컬렉션을 사용한다면, 기본값을 사용하더라도(즉, assign, retain 혹은 copy를 지정하지 않더라도) 프라퍼티의 타입이 NSCopying을 따르는 클래스가 아닌 이상 경고를 발생하지 않는다. 기본값은 주로 당신이 원하는 값이지만; 프라퍼티 타입이 복사될 수 있다면, 캡슐화를 유지하기 위해 오브젝트의 private copy를 만들기를 원할 때가 종종 있다.
[편집] Property Re-declaration
서브클래스에서 프라퍼티를 재정의할 수 있지만, 그 서브클래스 내에서 그 어트리뷰트를 다시 반복해 줘야 한다(readwrite에 대해 readonly는 예외를 가진다). 카테고리나 프로토콜에서 선언한 프라퍼티에 대해서도 유효하다 - 프라퍼티는 카테고리나 프로토콜에서 주로 재선언되는 반면에, 프라퍼티의 어트리뷰트는 완전히 다시 반복되어야 한다.
하나의 클래스에서 프라퍼티를 readonly로 선언했다면, 클래스 익스텐션("Extensions"을 보라), 프로토콜 혹은 서브클래스에서 readwrite로 재선언할 수 있다 - "Subclassing with Properties"를 보라. 클래스 익스텐션 재선언의 경우, 프라퍼티가 재선언되었다는 사실이 모든 @synthesize 구문보다 우선하므로 setter가 생성되게 한다. 읽기 전용 프라퍼티를 read/write로 재선언하는 능력은 두가지 일반적인 구현 패턴을 가능하게 한다 : immutable 클래스에 대한 mutable 서브클래스(NSString, NSArray 그리고 NSDictionary가 모두 예가 될 수 있겠다)와 readonly이지만 클래스 내부적으로는 private하게 readwrite를 구현하는 퍼블릭 API를 가지는 프라퍼티. 다음의 예제는 클래스 익스텐션을 사용하여 퍼블릭 헤더에서 읽기전용으로 선언되었지만 private으로는 읽기/쓰기로 재선언한 프라퍼티를 제공하는 것을 보여준다.
// public header file
@interface MyObject : NSObject
{
NSString *language;
}
@property (readonly, copy) NSString *language;
@end
// private implementation file
@interface MyObject ()
@property (readwrite, copy) NSString *language;
@end
@implementation MyObject
@synthesize language;
@end
[편집] Performance and Threading
메소드 구현을 직접 제공한다면, 당신이 프라퍼티를 선언했다는 것은 그 효율성이나 쓰레드 안전성에 아무런 영향을 미치지 못한다.
생성된(synthesized) 프라퍼티를 사용한다면, 컴파일러에 의해 생성되는 메소드 구현은 당신이 제공하는 설정값에 의한다. 퍼포먼스와 쓰레딩에 영향을 주는 어트리뷰트 선언은 retain, assign, copy 그리고 nonatomic이다. 앞의 세개는 아래에서 예시한 것 처럼 set 메소드의 assignment 부분의 구현에만 영향을 준다 (구현은 다음 보기와 완전히 같지는 않을 것임을 유의하자):
// assign
property = newValue;
// retain
if (property != newValue)
{
[property release];
property = [newValue retain];
}
// copy
if (property != newValue)
{
[property release];
property = [newValue copy];
}
nonatomic 어트리뷰트의 효과는 환경에 따라 다르다. 기본적으로, 생성된 엑세서들은 atomic이다. managed 메모리 환경에서, atomic 비헤비어를 보장하려면 lock을 사용해야 한다; 게다가 리턴된 오브젝트는 retain되고 autorelease된다. 이런 엑세서가 빈번하게 발생된다면, 퍼포먼스에 엄청난 충격을 줄 수 있다. 가베지 컬렉션 환경하에서, 대부분의 생성된 메소드들은 이러한 오버헤드를 발생시키지 않고 atomic하다.
atomic 구현의 목표는 robust 엑세서를 제공하는 것이라는 것을 이해하는 것이 중요하다. - 이것은 당신 코드의 정확성(correctness)을 보장하지는 않는다. "atomic"이 프라퍼티에 대한 엑세스가 쓰레드에 안전하다는 것을 의미하긴 해도, 클래스의 모든 프라퍼티를 atomic으로 만드는 게 당신의 클래스 또는 보다 일반적으로 당신의 오브젝트 그래프가 "스레드에 안전"하다는 것을 의미하지는 않는다 - 쓰레드 안전성은 개별 엑세서 메소드 차원에서 표현될 수 없다. 멀티-쓰레딩에 대한 보다 자세한 사항은 Multithreading Programming Topics를 보라.
[편집] Markup and Deprecation
프라퍼티는 C 스타일 수식어를 전반적으로 지원한다. 프라퍼티는 다음 예제에서 보이는 것과 같이 deprecated 될 수 있으며 __attribute__ 스타일의 마크업을 지원한다.
@property CGFloat x AVAILABLE_MAC_OS_X_VERSION_10_1_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_4; @property CGFloat y __attribute__((...));
[편집] Core Foundation
"Property Implementation Directives"에 적힌 것과 같이 오브젝트 타입이 아닌 것에 대해 retain 어트리뷰트를 지정할 수 없다. 그러므로, 만약 CFType 타입의 프라퍼티를 선언하고 엑세서를 다음 예제와 같이 생성했다면 :
@interface MyClass : NSObject
{
CGImageRef myImage;
}
@property(readwrite) CGImageRef myImage;
@end
@implementation MyClass
@synthesize myImage;
@end
managed 메모리 환경에서 생성되는 set 엑세서는 새 값을 인스턴스 변수에 단순하게 assign한다( 새 값이 retain되지 않고 이전 값이 release되지 않는다). 이는 전형적으로 잘 못된 것이므로, 메소드를 생성하도록(synthesize) 하지 말고, 직접 구현해야 한다.
가베지 컬렉션 환경하에서, 변수가 __strong으로 선언되었다면:
... __strong CGImageRef myImage; ... @property CGImageRef myImage;
엑세서는 적당하게 생성된다 - 이미지는 CFRetain된 게 아니겠지만, setter가 write barrier를 동작시킬 것이다.
[편집] Example
다음 예제는 몇가지 다른 방식으로 프라퍼티를 상요하는 법을 보여준다:
- Link 프로토콜은 next 프라퍼티를 선언한다.
- MyClass는 Link 프로토콜을 따르므로 내부적으로 역시 next 프라퍼티를 선언한다. MyClass 또한 몇가지 다른 프라퍼티를 선언한다.
- creationTimestamp와 next는 생성되지만 다른 이름을 가진 기존의 인스턴스 변수를 사용한다;
- name은 생성되고, 인스턴스 변수 synthesis를 사용한다( 인스턴스 변수 synthesis를 리콜하는 것은 32비트 런타임 사용시 지원되지 않는다 - "Property Implementation Directives"와 "Runtime Differences"를 보라);
- gratuitousFloat은 dynamic 지시어를 가지고 있다 - 이는 직접 메소드 구현을 이용한다.
- nameAndAge는 dynamic 지시어를 가지지 않지만, 기본값이다; 특정 이름(nameAndAgeAsString)의 직접 메소드 구현을 이용한다( 읽기 전용이므로, getter만 있으면 된다).
Listing 4-4 클래스의 프라퍼티를 선언하기
@protocol Link
@property id <Link> next;
@end
@interface MyClass : NSObject <Link>
{
NSTimeInterval intervalSinceReferenceDate;
CGFloat gratuitousFloat;
id <Link> nextLink;
}
@property(readonly) NSTimeInterval creationTimestamp;
@property(copy) __strong NSString *name;
@property CGFloat gratuitousFloat;
@property(readonly, getter=nameAndAgeAsString) NSString *nameAndAge;
@end
@implementation MyClass
@synthesize creationTimestamp = intervalSinceReferenceDate, name;
// synthesizing 'name' is an error in the fragile instance variable runtime
// in the non-fragile instance variable runtime, the instance variable is synthesized
@synthesize next = nextLink;
// uses instance variable "nextLink" for storage
@dynamic gratuitousFloat;
// will warn unless -gratuitousFloat and -setGratuitousFloat: occur in @implementation
- (CGFloat)gratuitousFloat
{
return gratuitousFloat;
}
- (void)setGratuitousFloat:(CGFloat)aValue
{
gratuitousFloat = aValue;
}
- (NSString *)nameAndAgeAsString
{
return [NSString stringWithFormat:@"%@ (%fs)", self.name,
[NSDate timeIntervalSinceReferenceDate] - intervalSinceReferenceDate];
}
- init
{
if (self = [super init])
{
intervalSinceReferenceDate = [NSDate timeIntervalSinceReferenceDate];
}
return self;
}
@end




