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

[Effective Objective-C] #10 연관 객체를 사용해 기존 클래스에 사용자 정의 데이터를 연관 지으라

by 돼지왕 왕돼지 2017. 8. 12.
반응형

 [Effective Objective-C] #10 연관 객체를 사용해 기존 클래스에 사용자 정의 데이터를 연관 지으라


출처 : Effective Objective-C

associated objects, mimic owning, non-owning, OBJC_ASSOCIATION_ASSIGN, OBJC_ASSOCIATION_COPY, OBJC_ASSOCIATION_COPY_NONATOMIC, OBJC_ASSOCIATION_RETAIN, OBJC_ASSOCIATION_RETAIN_NONATOMIC, objc_getAssociatedObject, objc_removeAssociatedObject, objc_setAssociatedObject, obj_AssocationPolicy, opaque pointer, retain cycle, static variable, storage policy, [Effective Objective-C] #10 연관 객체를 사용해 기존 클래스에 사용자 정의 데이터를 연관 지으라, 동일 포인터, 디버깅, 리테인 순환, 불투명 포인터, 비소유, 사용자 정의 데이터, 식별키, 연관 객체, 저장 정책, 정적 전역 변수, 최소 소유, 키


-

객체에 추가 정보를 연관 지을 필요가 가끔 있다.

보통 그 객체의 클래스의 하위 클래스를 만들고 그 하위 클래스에 추가 정보를 더할 수 있지만 항상 가능하진 않다.

클래스의 인스턴스가 여러분이 직접 생성하는 것이 아닌 특정 방법으로 생성될 수 있고, 이때는 해당 클래스 대신 여러분이 만든 하위 클래스가 생성되도록 바꿀 수 없기 때문이다.

이 떄문에 손쉽게 사용할 수 있는 오브젝티브-C 의 강력한 기능인 연관 객체( Associated Objects ) 가 생겨났다.



-

객체는 식별 키를 사용해 다른 객체를 연관 지을 수 있다.

또한 연관되는 객체를 저장하는 값에 대한 메모리 관리 시맨틱을 지정하는 저장 정책(storage policy)을 지정할 수 있다.

저장 정책은 열거자 obj_AssocationPolicy 로 정의할 수 있다.


OBJC_ASSOCIATION_ASSIGN     // assign

OBJC_ASSOCIATION_RETAIN_NONATOMIC     // nonatomic, retain

OBJC_ASSOCIATION_COPY_NONATOMIC      // nonatomic, copy

OBJC_ASSOCIATION_RETAIN     // retain

OBJC_ASSOCIATION_COPY     // copy



-

다음 메서드를 사용하여 연관을 관리할 수 있다.

void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy )

     // 객체에 주어진 값을 주어진 키 및 정책으로 연관 짓는다.


id objc_getAssociatedObject(id object, void *key)

     // 주어진 키로 객체와 연관된 값을 추출한다.


void objc_removeAssociatedObject(id object)

     // 객체의 모든 연관을 제거한다.


-

연관된 객체에 접근하는 것은 객체가 NSDictionary 라 가정하면 [object setObject:value[, [object objectForKey:Key] 를 호출하는 것과 기능적으로 비슷하다.

중요한 차이점은 키를 단순히 불투명 포인터(opaque pointer)로 다룬다는 것이다.

opaque pointer 는 컴파일 시점에 강한 타입 검사를 할 수 없지만, 실제로 포인터가 가리키는 데이터 구조를 숨길 수 있어 좀 더 유연하게 구현할 수 있다.

물론 예상하지 못한 데이터 구조라 에러가 날 확률은 높다.



-

연관된 객체의 키는 완전한 동일한 포인터여야만 같은 것으로 간주한다.

이 때문에 키는 보통 정적(static) 전역 변수를 사용한다.




연관 객체 사용 예제


-

UIAlertView 는 사용자에게 알람을 보여주는 표준 뷰를 제공한다.

그리고 델리게이트 프로토콜이 있어, 사용자가 뷰를 닫으려고 버튼을 눌렀을 때 특정 로직을 처리한다.

델리게이트를 사용하는 것은 알람을 생성하는 코드와 버튼을 눌렀을 때 처리하는 코드를 분리하기 때문에 읽기가 어렵다.



-

버튼을 눌렀을 때 처리할 로직이 알람을 생성할 때 결정될 수 있으면 좀 더 간단해질 수 있다.

여기가 바로 연관 객체가 사용될 수 있는 곳이다.

알람을 생성할 때 로직을 처리하는 블록을 알람 객체에 연관 짓는다.

그리고 델리게이트 메서드가 실행될 때 알람과 연관된 블록을 실행한다.

// outside function

static void *EOCMyAlertViewKey = “EOCMyAlertViewKey”;


- (void)aFunction{

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@“Question"

     message:@“What do you want to do?"

     delegate:self

     cancelButtonTitle:@“Cancel"

     otherButtonTitles:@“Continue”, nil];


void (^block)(NSInteger) = ^(NSInteger buttonIndex){

     if ( buttonIndex == 0 ){

          [self doCancel];

     } else{

          [self doContinue];

     }

};


objc_setAssociatedObject( alert, EOCMyAlertViewKey, block, OBJC_ASSOCIATION_COPY );

}


- (void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{

     void (^block)(NSInteger) = objc_getAssciatedObject(alertView, EOCMyAlertViewKey);

     block(buttonIndex);

}



-

위의 코드에서는 주의해야 할 부분이 있다.

블록이 캡처(capture)된면 리테인 순환(retain cycle)이 쉽게 될 수 있다.


이와 같이 연관짓기 방법은 강력하지만 더 이상의 대안이 없을 때만 사용해야 한다.

이 방법을 과도하게 사용하면 순식간에 코드가 감당하기 어렵게 되고 디버깅도 어려워진다.

메모리 관리 속성이 인터페이스를 정의할 때가 아닌 연관될 때 정의되는 것처럼, 연관 객체의 관계를 공식적으로 정의하는 부분이 없으므로 리테인 순환을 발견하기 어렵기 때문이다.


그래서 이 방법을 사용할 때는 주의해서 진행하라.



-

연관짓기 방법을 쓸 수 있다고 무조건 이 방법을 사용하면 안 된다.

이 방법의 대안은 UIAlertView 의 하위 클래스를 만들고 블록을 저장하는 프로퍼티를 추가하는 것이다.

알람 뷰(alert view)가 한 번 이상 사용된다면 연관 객체보다 이 방법을 사용하는 것이 낫다.




기억할 점


연관 객체는 객체 두 개를 연결하는 방법을 제공한다.


연관 객체의 메모리 관리 속성으로 최소 소유(mimic owning) 또는 비소유(non owning) 관계를 정의할 수 있다.


연관 객체는 다른 대안이 없을 때만 사용해야 한다.

연관 객체는 찾기 어려운 버그를 쉽게 만들기 때문이다.




반응형

댓글