Objective-C : 카테고리의 속성 / 인스턴스 변수


122

Objective-C의 Category에서 합성 속성을 만들 수 없기 때문에 다음 코드를 최적화하는 방법을 모릅니다.

@interface MyClass (Variant)
@property (nonatomic, strong) NSString *test;
@end

@implementation MyClass (Variant)

@dynamic test;

- (NSString *)test {
    NSString *res;
    //do a lot of stuff
    return res;
}

@end

시험 방법은 런타임에 여러 번 호출하고 난 결과를 계산하기 위해 물건을 많이하고 있어요. 일반적으로 합성 속성을 사용하여 메서드가 처음 호출 될 때 IVar _test에 값을 저장하고 다음에이 IVar를 반환합니다. 위 코드를 어떻게 최적화 할 수 있습니까?


2
일반적으로 수행하는 작업을 범주 대신 MyClass 기본 클래스에 추가하는 것이 어떻습니까? 그리고 더 나아가려면 백그라운드에서 무거운 작업을 수행하고 프로세스가 알림을 시작하거나 프로세스가 완료되면 MyClass에 대한 처리기를 호출하십시오.
제레미

4
MyClass는 Core Data에서 생성 된 클래스입니다. 생성 된 클래스 내부의 사용자 지정 개체 코드 만 있으면 코어 데이터에서 클래스를 다시 생성하면 사라집니다. 이 때문에 카테고리를 사용하고 있습니다.
dhrm

1
제목에 가장 적합한 질문을 받아 들일 수 있습니까? ( '카테고리의 속성')
hfossli

왜 그냥 서브 클래스를 만들지 않습니까?
스콧 Zhu의

답변:


124

@lorean의 방법이 작동하지만 (참고 : 이제 답변이 삭제됨) 스토리지 슬롯이 하나만 있습니다. 따라서 여러 인스턴스에서 이것을 사용하고 각 인스턴스가 고유 한 값을 계산하도록하려면 작동하지 않습니다.

다행히도 Objective-C 런타임에는 원하는 작업을 정확히 수행 할 수있는 Associated Objects 라는 것이 있습니다.

#import <objc/runtime.h>

static void *MyClassResultKey;
@implementation MyClass

- (NSString *)test {
  NSString *result = objc_getAssociatedObject(self, &MyClassResultKey);
  if (result == nil) {
    // do a lot of stuff
    result = ...;
    objc_setAssociatedObject(self, &MyClassResultKey, result, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  }
  return result;
}

@end


5
@DaveDeLong이 솔루션에 감사드립니다! 아니 매우 아름다운하지만 :) 작동
dhrm

42
"별로 아름답 지 않다"? 이것이 Objective-C의 아름다움입니다! ;)
Dave DeLong

6
좋은 대답입니다! @selector(test)여기에 설명 된대로 키로 사용하여 정적 변수를 제거 할 수도 있습니다. stackoverflow.com/questions/16020918/…
Gabriele Petronella 2013

1
- @HotFudgeSunday는 빠른에서 작업을 수행 생각 stackoverflow.com/questions/24133058/...
스콧 Corscadden

174

.h 파일

@interface NSObject (LaserUnicorn)

@property (nonatomic, strong) LaserUnicorn *laserUnicorn;

@end

.m 파일

#import <objc/runtime.h>

static void * LaserUnicornPropertyKey = &LaserUnicornPropertyKey;

@implementation NSObject (LaserUnicorn)

- (LaserUnicorn *)laserUnicorn {
    return objc_getAssociatedObject(self, LaserUnicornPropertyKey);
}

- (void)setLaserUnicorn:(LaserUnicorn *)unicorn {
    objc_setAssociatedObject(self, LaserUnicornPropertyKey, unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
}

@end

일반 속성처럼-점 표기법으로 액세스 가능

NSObject *myObject = [NSObject new];
myObject.laserUnicorn = [LaserUnicorn new];
NSLog(@"Laser unicorn: %@", myObject.laserUnicorn);

더 쉬운 구문

또는 다음 @selector(nameOfGetter)과 같이 정적 포인터 키를 만드는 대신 사용할 수 있습니다 .

- (LaserUnicorn *)laserUnicorn {
    return objc_getAssociatedObject(self, @selector(laserUnicorn));
}

- (void)setLaserUnicorn:(LaserUnicorn *)unicorn {
    objc_setAssociatedObject(self, @selector(laserUnicorn), unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
}

자세한 내용은 https://stackoverflow.com/a/16020927/202451을 참조 하십시오.


4
좋은 기사. 주목해야 할 한 가지는 기사의 업데이트입니다. 2011 년 12 월 22 일 업데이트 : 연결 키는 문자열이 아니라 void 포인터 void * key라는 점에 유의해야합니다. 즉, 관련 참조를 검색 할 때 정확히 동일한 포인터를 런타임에 전달해야합니다. C 문자열을 키로 사용한 다음 문자열을 메모리의 다른 위치에 복사하고 복사 된 문자열에 대한 포인터를 키로 전달하여 연관된 참조에 액세스하려고하면 의도 한대로 작동하지 않습니다.
Mr Rogers

4
당신은 정말로 @dynamic objectTag;. @dynamicsetter & getter가 다른 곳에서 생성되지만이 경우에는 바로 여기에서 구현됩니다.
IluTov

@NSAddict 사실! 결정된!
hfossli 2013

1
수동 메모리 관리를위한 laserUnicorns의 Dealloc은 어떻습니까? 이것은 메모리 누수입니까?
Manny


32

주어진 대답은 훌륭하게 작동하며 내 제안은 너무 많은 상용구 코드를 작성하지 않는 확장 일뿐입니다.

카테고리 속성에 대한 getter 및 setter 메서드를 반복적으로 작성하지 않도록이 답변은 매크로를 소개합니다. 또한이 매크로는 다음과 같은 원시 형 속성의 사용 용이성 int또는 BOOL.

매크로가없는 전통적인 접근 방식

전통적으로 다음과 같은 카테고리 속성을 정의합니다.

@interface MyClass (Category)
@property (strong, nonatomic) NSString *text;
@end

그런 다음 관련 객체get 선택기 를 키로 사용하여 getter 및 setter 메서드를 구현해야합니다 ( 원래 답변 참조 ).

#import <objc/runtime.h>

@implementation MyClass (Category)
- (NSString *)text{
    return objc_getAssociatedObject(self, @selector(text));
}

- (void)setText:(NSString *)text{
    objc_setAssociatedObject(self, @selector(text), text, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

내가 제안한 접근 방식

이제 매크로를 사용하여 대신 작성합니다.

@implementation MyClass (Category)

CATEGORY_PROPERTY_GET_SET(NSString*, text, setText:)

@end

매크로는 다음과 같이 정의됩니다.

#import <objc/runtime.h>

#define CATEGORY_PROPERTY_GET(type, property) - (type) property { return objc_getAssociatedObject(self, @selector(property)); }
#define CATEGORY_PROPERTY_SET(type, property, setter) - (void) setter (type) property { objc_setAssociatedObject(self, @selector(property), property, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }
#define CATEGORY_PROPERTY_GET_SET(type, property, setter) CATEGORY_PROPERTY_GET(type, property) CATEGORY_PROPERTY_SET(type, property, setter)

#define CATEGORY_PROPERTY_GET_NSNUMBER_PRIMITIVE(type, property, valueSelector) - (type) property { return [objc_getAssociatedObject(self, @selector(property)) valueSelector]; }
#define CATEGORY_PROPERTY_SET_NSNUMBER_PRIMITIVE(type, property, setter, numberSelector) - (void) setter (type) property { objc_setAssociatedObject(self, @selector(property), [NSNumber numberSelector: property], OBJC_ASSOCIATION_RETAIN_NONATOMIC); }

#define CATEGORY_PROPERTY_GET_UINT(property) CATEGORY_PROPERTY_GET_NSNUMBER_PRIMITIVE(unsigned int, property, unsignedIntValue)
#define CATEGORY_PROPERTY_SET_UINT(property, setter) CATEGORY_PROPERTY_SET_NSNUMBER_PRIMITIVE(unsigned int, property, setter, numberWithUnsignedInt)
#define CATEGORY_PROPERTY_GET_SET_UINT(property, setter) CATEGORY_PROPERTY_GET_UINT(property) CATEGORY_PROPERTY_SET_UINT(property, setter)

매크로 CATEGORY_PROPERTY_GET_SET는 주어진 속성에 대한 getter 및 setter를 추가합니다. 읽기 전용 또는 쓰기 전용 속성은 각각 CATEGORY_PROPERTY_GETCATEGORY_PROPERTY_SET매크로를 사용합니다 .

원시 유형은 좀 더주의가 필요합니다.

기본 유형은 객체가 아니므로 위의 매크로에는 unsigned int속성 유형으로 사용하기위한 예제가 포함되어 있습니다 . 정수 값을 NSNumber개체 로 래핑하여 수행 합니다. 따라서 사용법은 이전 예제와 유사합니다.

@interface ...
@property unsigned int value;
@end

@implementation ...
CATEGORY_PROPERTY_GET_SET_UINT(value, setValue:)
@end

이 패턴에 따라, 당신은 단순히 또한 지원하기 위해 더 많은 매크로를 추가 할 수 있습니다 signed int, BOOL등 ...

한계

  1. 모든 매크로는 OBJC_ASSOCIATION_RETAIN_NONATOMIC기본적으로 사용 됩니다.

  2. App Code 와 같은 IDE 는 현재 속성의 이름을 리팩토링 할 때 setter의 이름을 인식하지 못합니다. 직접 이름을 변경해야합니다.


1
#import <objc/runtime.h>그렇지 않으면 범주 .m 파일에서 잊지 마십시오 . 컴파일 시간 오류 : C99에서 'objc_getAssociatedObject'함수의 암시 적 선언이 잘못되었습니다 . stackoverflow.com/questions/9408934/...
Sathe_Nagaraja


3

iOS 9에서만 테스트 됨 예제 : UINavigationBar (Category)에 UIView 속성 추가

UINavigationBar + Helper.h

#import <UIKit/UIKit.h>

@interface UINavigationBar (Helper)
@property (nonatomic, strong) UIView *tkLogoView;
@end

UINavigationBar + Helper.m

#import "UINavigationBar+Helper.h"
#import <objc/runtime.h>

#define kTKLogoViewKey @"tkLogoView"

@implementation UINavigationBar (Helper)

- (void)setTkLogoView:(UIView *)tkLogoView {
    objc_setAssociatedObject(self, kTKLogoViewKey, tkLogoView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIView *)tkLogoView {
    return objc_getAssociatedObject(self, kTKLogoViewKey);
}

@end

-2

사용하지 않는 또 다른 가능한 해결책 Associated Objects은 다음과 같이 범주 구현 파일에 변수를 선언하는 것입니다.

@interface UIAlertView (UIAlertViewAdditions)

- (void)setObject:(id)anObject;
- (id)object;

@end


@implementation UIAlertView (UIAlertViewAdditions)

id _object = nil;

- (id)object
{
    return _object;
}

- (void)setObject:(id)anObject
{
    _object = anObject;
}
@end

이러한 종류의 구현의 단점은 객체가 인스턴스 변수가 아니라 클래스 변수로 작동한다는 것입니다. 또한 속성 속성을 할당 할 수 없습니다 (예 : OBJC_ASSOCIATION_RETAIN_NONATOMIC과 같은 관련 개체에서 사용됨).


질문은 인스턴스 변수에 대해 묻습니다. 귀하의 솔루션은 Lorean처럼
hfossli

1
정말?? 당신이하는 일을 알고 계셨습니까? 파일에 대해 독립적 인 내부 변수에 액세스하기 위해 한 쌍의 getter / setter 메서드를 만들었지 만 클래스 개체, 즉 두 개의 UIAlertView 클래스 개체를 할당하면 개체 값이 동일합니다!
Itachi
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.