본문 바로가기
프로그래밍 놀이터/iOS

[Effective Objective-C] #6 프로퍼티를 이해하라

by 돼지왕 왕돼지 2017. 7. 27.
반응형

 [Effective Objective-C] #6 프로퍼티를 이해하라


출처 : Effective Objective-C

. 문법, 2.0, @dynamic, @private, @property, @property 문법, @public, @synthesize, ABI, accessor method, assign, Atomic, autosynthesis, cffloat, COPY, CoreData, dealloc, effective objective-c, getter=, getter=isOn, init 메서드, IOS, is, LOCK, nil, nonatomic, nonfragile, NSInteger, NSManagedObject, Objective-C, OS X, property, property semantic, readonly, readwrite, release, retain, scalar, semantics, setter=, strong, thread-safe, underscore, unsafe, unsafe_unretained, weak, [Effective Objective-C] #6 프로퍼티를 이해하라, 게터 세터, 권장사항, 규약, 네이밍, 락, 메모리 관리 시멘틱 프로퍼티 속성, 밑줄, 비파손성, 성능 문제, 성능 병목, 스레드-안전, 스칼라 타입, 시맨틱, 앱 바이너리 인터페이스, 오프셋, 원자성, 원자성 프로퍼티 속성, 이름 변경, 이름 패턴, 인스턴스 변수, 읽기/쓰기 프로퍼티 속성, 자동 생성, 자동 합성, 재컴파일, 점 문법, 접근자 메서드, 추가, 캡슐화, 컴파일러, 특별 인스턴스 변수, 프로퍼티, 프로퍼티 시맨틱, 하드코딩, 할당



-

프로퍼티는 객체가 포함한 데이터를 캡슐화하는 방법을 제공하는 Objective-C 의 기능이다.



-

인스턴스 변수는 항상 접근자 메서드(accessor method)를 통해 접근한다.

Objective-C 2.0 배포판에 프로퍼티라는 기능으로 포함되었다.

이 기능으로 개발자는 접근자 메서드들을 자동으로 생성하라고 컴파일러에 알려줄 수 있다.

프로퍼티는 점(.) 문법이라는 새로운 방법을 제공한다.

점 문법 덕분에 클래스에 저장된 데이터에 접근하는 방법이 좀 덜 장황해졌다.



-

@interface EOCPerson : NSObject{

@public

     NSString *_firstName;

     NSString *_lastName;

@private

     NSString *_someInternalData;

}


위와 같은 방법으로 public, private 변수를 정의하는 방법을 최신 Objective-C 에서는 거의 사용되지 않는다.

위 방법의 문제는 객체의 배치가 컴파일 시간에 결정된다는 것이다.

컴파일러는 _firstName 변수에 접근하는 모든 곳을 그 객체가 거주하는 메모리의 오프셋으로 하드코딩한다.

이 방법은 또 다른 인스턴스 변수를 추가하기 전까지는 잘 동작한다.



-

컴파일 시간에 오프셋을 계산하는 코드는 클래스 정의를 변경했을 때, 재컴파일하기 전까지는 깨진 상태이다.

예를 들어 과거의 클래스 정의를 사용하는 라이브러리가 있을 수 있다.

링크한 코드가 새로운 클래스 정의를 사용하면 실행 시간에 불일치(incompatibility)가 발생할 수 있다.

이 문제를 해결하기 위해 언어는 다양한 기술을 발명했다.

Objective-C가 채택한 방법은 오프셋을 저장하고 있는 클래스 객체를 가진 특별 인스턴스 변수를 만드는 방법이다.

그러면 오프셋이 실행 시간에 찾아진다.

그렇기 때문에 클래스 정의가 바뀌어도 저장된 오프셋이 실행 시간에 찾아진다.

그렇기 때문에 클래스 정의가 바뀌어도 저장된 오프셋도 같이 갱신된다.



-

클래스에 인스턴스 변수를 실행 시간에도 추가할 수 있다.

이를 비파손성(nonfragile) 애플리케이션 바이너리 인터페이스(ABI) 라고 한다.

ABI 는 다른 여러가지도 포함하지만, 코드가 어떻게 생성되어야 하는지에 대한 규약이다.



-

인스턴스 변수를 직접 사용하기보다 접근자 메서드를 사용하기를 장려하는 것은 이 문제를 해결하는 또 다른 방법이다.

접근자 메서드를 직접 작성할 수 있지만 사실 Objective-C 스타일의 접근자 메서드는 엄격한 이름 패턴을 따른다.

이 엄격한 네이밍이 있기 때문에 접근자 메서드를 자동으로 생성하는 방법을 제공하는 언어 생성자를 만드는 것이 가능하다.

이것이 바로 @property 문법이다.



-

컴파일러가 마치 프로그래머가 직접 접근자 메서드를 호출한 것처럼 점 문법을 접근자 메서드를 호출하는 것으로 바꾼다.

그래서 점 문법을 사용하는 것과 접근자 메서드를 직접 호출하는 것은 완전히 동일하다.



-

프로퍼티에서는 좀 더 많은 일이 일어난다.

프로터피로 선언했으면 컴파일러는 자동 합성(autosynthesis)이라는 프로세스를 통해 자동으로 접근자 메서드들에 대한 코드를 작성할 것이다.

이러한 일들은 컴파일러가 컴파일 시간에 하기 때문에 에디터로 자동 생성된 메서드의 소스 코드를 볼 수 없다.

코드가 만들어질 때 컴파일러는 주어진 타입과 프로퍼티 이름에 밑줄 문자(underscore, _)를 접두어로 붙인 인스턴스 변수를 자동으로 클래스에 추가한다.



-

클래스 구현 파일에서 @synthesize 문법을 이용해 인스턴스 변수의 이름을 다음과 같이 변경할 수 있다.


@implementation EOCPerson

@synthesize firstName = _myFirstName;

@end


인스턴스 변수의 기본 이름을 쓰지 않고 다른 이름으로 변경하는 것은 드문 경우지만,

인스턴스 변수 이름에 밑줄을 붙이는 것이 싫으면 이 방법을 이용해 원하는 이름으로 바꿀 수 있다.

그러나 기본 이름을 사용하는 것이 권장된다.

이는 네이밍 관례를 따르는 많은 사람이 쉽게 코드를 읽을 수 있게 하기 때문이다.



-

컴파일러가 접근자 메서드를 자동 생성하는 것을 원하지 않는다면 스스로 접근자 메서드를 구현할 수 있다.

그러나 접근자 중 일부만 구현한다면 나머지는 여전히 컴파일러가 자동 생성할 것이다.



-

자동 생성을 막는 또 다른 방법은 @dynamic 키워드를 사용하는 것이다.

이것은 프로퍼티에 대한 인스턴스 변수와 접근자를 자동으로 생성하지 말라고 컴파일러에 알려준다.

또 프로퍼티에 접근하는 코드를 컴파일 할 때 컴파일러는 접근자 메서드가 정의되어 있지 않다는 사실을 무시하고 실행 시간에 사용할 수 있을 거라고 믿는다.


예를 들어 CoreData 의 NSManagedObject 의 하위 클래스를 만들 경우 접근자 메서드들은 실행 시간에 동적으로 생성된다.

NSManagedObject 가 이 방법을 채택했는데, 프로퍼티가 인스턴스 변수가 아니기 때문이다.

대신 데이터베이스 같은 것으로부터 가져온 데이터를 사용한다.


@implementation EOCPerson

@dynamic firstName, lastName;

@end


이 클래스에서 어떠한 접근자 메서드나 인스턴스 변수도 자동 생성되지 않을 것이다.

컴파일러는 경고를 보내지 않을 뿐 아니라 프로퍼티에 대한 접근자를 사용해도 마찬가지로 에러를 발생시키지 않는다.






원자성 프로퍼티 속성


-

자동 생성된 접근자 메서드는 기본적으로 메서드가 원자적으로 동작하게 만드는 데 필요한 락(lock) 을 포함한다.

속성을 nonatomic 으로 하면 락이 사용되지 않는다.

atomic 속성이 없더라도(nonatomic 속성이 없으면 atomic 으로 여겨진다.) 명시적으로 원했을 경우를 대비해 컴파일 에러 없이 원자성이 적용된다.

스스로 접근자 메서드를 정의하는 경우는 메서드가 원자적으로 동작하도록 구현해야 한다.




읽기/쓰기 프로퍼티 속성


-

readwrite

     게터, 세터 모두 사용한다.

     프로퍼티가 자동 생성될 경우 컴파일러는 두 메서드 모두 생성한다.



-

readonly

     게터만 사용한다.

     프로퍼티를 외부에 읽기 전용으로 공개하고 싶을 때 사용할 수 있으나 클래스 확장 카테고리에서는 내부적으로 읽기/쓰기로 재선언된다.




메모리 관리 시멘틱 프로퍼티 속성


-

오직 세터에만 해당된다.

세터는 새로운 값을 리테인(retain)해야 하나? 아니면 간단히 하부의 인스턴스 변수에 할당만 해야 하나를 지정한다.



-

assign

     CFFloat, NSInteger 같은 스칼라(scalar) 타입에 사용하는 간단한 대입 연산이다.



-

strong

     프로퍼티가 값을 소유한다는 것을 나타낸다.

     새로운 값이 설정되면 먼저 그 값을 리테인하고 원래 값은 릴리스(release)한다.

     그런 다음 리테인한 새 값을 설정한다.



-

weak

     프로퍼티가 값을 소유하지 않는 것을 나타낸다.

     새로운 값이 설정되면 이 값은 리테인하지 않을 뿐 아니라 이전 값을 릴리스하지도 않는다.

     이것은 assign 과 비슷하지만 프로퍼티가 가리키던 객체는 언제든 파괴될 수 있고 파괴되면 프로퍼티는 nil 로 설정된다.



-

unsafe_unretained

     weak 과 같지만 타입이 소유하지 않는 관계인 객체 타입일 때 사용된다.

     이 관계는 대상 객체가 파괴되었을 때 weak 와는 다르게 값이 nil 로 설정되지 않는다. (unsafe)



-

copy

     strong 과 비슷하게 값을 소유한다고 나타낸다.

     그러나 값을 리테인하는 대신 복사한다.

     이것은 타입이 캡슐화가 잘 되어 있는 NSString* 일 때 가끔 사용된다.

     가변일 수 있는 객체는 반드시 copy 속성을 가져야 한다.




메서드 이름 프로퍼티


-

getter=<name>

     게터 이름을 정의한다.

     불린 속성을 위한 게터는 보통 is 접두어를 붙인다.


@property (nonatomic, getter=isOn) BOOL on;



-

setter=<name>

     세터 이름을 정의한다.

     이 메서드는 보통 사용되지 않는다.



-

자동 생성되는 접근자 메서드를 세밀히 다루기 위해 이러한 속성을 사용할 수 있다.

그러나 접근자 메서드를 직접 구현할 때는 접근자에 명시된 속성을 만족하도록 스스로 구현해야 한다.



-

프로퍼티를 설정할 수 있는 세터가 아닌 다른 메서드에서도 프로퍼티 정의에 명시되어 있는 시맨틱을 따르도록 보장하는 것도 중요하다.

예를 들어 init method 에서 인자들을 전달받을 때 property 설정이 copy 였다면 이곳에서도 copy 로 유지하는 그런 맥락이다.



-

절대로 init 메서드(또는 dealloc)에 스스로 만든 접근자 메서드를 사용해서는 안 된다.



-

atomic  접근자는 원자성을 보장하기 위한 락을 포함한다.

이는 두 개의 스레드가 같은 값을 읽고 쓸 때 프로퍼티의 값이 어떠한 순간에도 유효하다는 것을 의마한다.

락이 없거나 nonatomic 인 프로퍼티 값은 한 스레드가 값을 읽고 있는 동시에 다른 스레드가 그 값을 쓰는 것이 가능하다.

그런 일이 일어난다면 읽으려는 값이 유효하지 않을 수 있다.



-

iOS 용만 개발했다면 모든 프로퍼티가 nonatomic 으로 선언됨을 알 수 있을 것이다.

그 이유는 역사적으로 락은 iOS 에 성능 문제를 일으키는 부하를 가져온다.

게다가 보통은 원자성이 필요 없다.

더 높은 레벨의 스레드-안전을 보장하지 않기 때문이다.



-

맥 OS X 에서는 보통 atomic 프로퍼티 접근이 성능 병목이 되지 않는다.



-

기억할 점

     @property 문법은 객체의 데이터 캡슐화를 정의하는 방법을 제공한다.


     저장되는 데이터를 위한 정확한 방법(semantics)을 제공하기 위해 속성을 사용하라.


     인스턴스 변수에 대한 모든 프로퍼티에는 따라야 하는 시맨틱을 반드시 정의하라.


     iOS 에서는 nonatomic 을 사용하리. atomic 을 사용하면 여러 가지 성능 문제를 일으킨다.




반응형

댓글