본문 바로가기

이펙티브 오브젝티브 C

[ObjectiveC] Property에 대해 알아보자

- 목적 : 현재 Effective Objective-C 2.0 책을 통해 공부하고 있지만, 현업 프로젝트에서 objc_getAssociatedObject 라는 오브젝티브 C의 제공 기능으로 테이블 뷰 인스턴스에 레지스트된 reuse identifier를 관리할 수 있게되었다. 허나, 해당 함수는 아래와 같은 형태로 메모리 관리 시맨틱 정책을 파라미터로 받기 때문에, 프로퍼티의 메모리 관리 시맨틱 + 원자성 옵션 등에 대해서 정확히 이해하고 넘어가기 위함이다.

 

 

[1] Property 도입 배경

오브젝티브C에서 클래스의 인스턴스 변수는 접근자 메서드를 통해 이뤄진다.

접근자 메서드란 인스턴스 변수를 읽고(getter), 쓰는(setter) 클래스에 속해있는 함수를 의미한다.

그리고, 이런 특정 인스턴스 변수에 지어지는 getter, setter는 엄격한 이름체계가 있었다.

그리고, setter를 통해 새로운 값이 들어왔을 때의 인스턴스 변수가 해당 값을 소유하는 retain 형태등까지

setter메서드에서 작업자가 모두 수동으로 해줘야 했다.

이런 상황에서 Objective C 2.0에는 @property라는 정의된 형태가 생겼다.

 

 

[2] Property란 무엇을 하는 녀석인가

결론부터 말하고 시작하면

"property는 뒤에 선언된 자료형, 이름을 통해 컴파일러가 컴파일 시점에 인스턴스 변수/접근자 메서드를 자동 생성하는 것"

이다.

그렇기 때문에 프로퍼티는 점(.) 문법으로 접근하게 되는데컴파일러는 점 문법을 통해 접근해서 get/set에 따라 각 접근자 메서드로 치환해서 실행한다는 것이다.

 

물론, 인스턴스 변수나 접근자 메서드를 임의로 구현할 수도 있다.이 때는 각 아래의 선언을 클래스 구현부에서 진행해주면 된다.

 

@synthesize [프로퍼티] = [새 인스턴스 이름]컴파일러가 프로퍼티의 인스턴스를 생성할 때, 위와 같이 선언되있으면 새 인스턴스 이름으로 생성한다.단, "_" 가 앞에 붙는 형태의 인스턴스 변수 관례는 범적으로 이해하기 쉬운 패턴이므로 지양하자.

 

@dynamic 프로퍼티컴파일러에서 자동으로 접근자 메서드, 인스턴스 변수를 생성하는 것을 막는 키워드다.하지만, 해당 선언을 하고 구현하지 않는다 하여 컴파일 에러가 발생하진 않는다.구현되지 않은 부분은 프로퍼티 측 정의에 따라 생성되기 때문.

 

 

[3] 가장 중요한 프로퍼티의 속성들

프로퍼티 뒤로 옵션으로써 설정할 수 있는 속성들의 종류에는 4가지가 있다.

 

 

1. 원자성

원자성은 특정 변수에 대한 접근이 발생할 때 Lock을 걸어 멀티 스레드 환경에서 다른 스레드가 접근하지 못하게 하는 것이다.

Lock을 걸어 다른 스레드에서 접근이 불가능하기 때문에 성능저하가 발생할 수 있으나,

항상 값의 무결성을 보장한다.

저자는 iOS 환경에서는 모든 프로퍼티에 대해 nonatomic을 사용할 것을 강조했다.

역시 성능저하부분 때문이다.

(MacOS는 atomic을 사용한다고 해서 병목현상이 발생하진 않는다고 함)

 

원자성 옵션 종류 의미
atomic 해당 프로퍼티에 대한 접근이 발생했을 때 Lock을 걸어 접근한다. (프로퍼티 속성 정의시 생략시 디폴트 값)
nonatomic Lock을 걸지않고 멀티스레드 환경에서 프로퍼티 접근을 할 수 있게 한다. (iOS 환경에서 디폴트 값)

 

2. 읽기/쓰기 옵션

프로퍼티에 대한 접근자 메서드 생성의 제약이다.

 

읽기/쓰기 옵션 의미
readwrite 컴파일러가 프로퍼티의 접근자 메서드를 생성할 때, 게터 및 세터를 모두 생성한다.
readonly 컴파일러가 프로퍼티의 접근자 메서드를 생성할 때 게터만 생성한다.

 

3. 메모리 관리 시멘틱

프로퍼티는 데이터를 캡슐화한다.

데이터의 인스턴스 변수, 접근자 메서드를 생성하는 키워드이므로 캡슐화한다고 볼 수 있다.

그리고 모든 데이터는 구체적인 소유권 시멘틱이 있어야 한다.

그리고 이러한 소유권 시멘틱은 오롯이 게터에만 해당된다.

예를 들면, 세터는새로운 값을 리테인(Retain)해야하나?

아니면 간단히 하부의 인스턴스 변수에 할당(Assign)만 해야하나?

프로퍼티로 선언된 데이터의 접근자 메서드를 자동 생성할 때,

위와 같이 소유권에 따라 컴파일러는 자동으로 적절한 setter 메서드를 생성한다.

(Retain이면 retain, release하는 로직이라던지 copy이면 copy를 호출한다던지..)

 

메모리 관리 시멘틱 설명
assign 세터는 CGFloat, NSInteger와 같은 스칼라 타입에 사용하는 간단한 대입연산이다.
(스칼라 타입: 단 하나의 데이터만 저장할 수 있는 데이터 형태)
strong 프로퍼티가 값을 소유한다는 것을 나타낸다. 새로운 값이 설정되면 먼저 그 값을 리테인하고 원래 값은 릴리스한다. 그런 다음 리테인 한 새 값을 설정한다.
weak 프로퍼티가 값을 소유하지 않고 참조만 하는 형태이다. 새로운 값에 대해 리테인하지도 않고, 기존 값을 릴리스하지도 않는다. 이런 의미에서 assign과 비슷해보이지만, 참조하고 있는 값이 릴리스되면 해당 프로퍼티의 값은 nil로 셋된다. (=Swift weak와 동일)
unsafe_unretained 이것은 assing과 같지만 타입이 소유하지 않는 관계(리테인하지 않는)인 객체 타입일 때 사용한다. 이 관계는 대상 객체가 파괴되었을 때 weak와는 다르게 값이 nil로 설정되지 않는다. (데이터 타입이 소유하지 않는 관계라는 말은 Swift의 struct/enum과 같이 Value참조일 때는 리테인 할 필요가 없다. 하지만, 참조하고 있는 객체가 릴리스가 되어도 nil이 안된다는 점에서 unowned와 같은 느낌으로 보여진다.)
copy strong과 비슷하게 값을 소유한다고 나타낸다. 그러나 값을 리테인하는 대신 복사한다. 이것은 타입이 캡슐화가 잘 되어 있는 NSString*일 때 가끔 사용된다. 세터로 전달된 값이 NSMutableString의 하위 클래스의 인스턴스일 수 있기 때문이다. 이와 같이 값이 가변 객체이면 프로퍼티에 설정된 후에 객체의 인지 없이 객체 내용이 변경될 수 있다(메모리로 참조하고 있기 때문). 그래서 불변 복사본을 인자로 전달하면 문자열이 객체 내부에서 변경되지 않음을 보장할 수 있다. 가변일 수 있는 객체는 반드시 copy 속성을 가져야 한다.

 

4. 메서드 이름

접근자 메서드의 이름은 다음 속성을 사용하여 제어할 수 있다.

프로퍼티 선언 내 메서드 이름 설명
getter=<name> 게터 이름을 정의한다. BOOL 속성을 위한 게터는 보통 is 접두어를 붙인다(이 때부터 is를 앞에 붙이면 BOOL을 반환하는 메서드라고 생각한 것 같다).
setter=<name> 사용하지 않는다.

getter=메서드이름 을 이용하면 아래처럼 가능하다.

 

// ObjectUsingProperty.h

#import <Foundation/Foundation.h>

@interface ObjectUsingProperty : NSObject
@property(nonatomic, readonly, getter=isDeleted)BOOL deleted;
@end


// ObjectUsingProperty.m

@implementation ObjectUsingProperty

-(void)change:(NSString*)newTitle
{
	if ([self isDeleted])
    {
    	_title = @"Can't set new title";
    }
    else
    {
    	_title = newTitle;
    }
}
@end