Objective-C: Property / instance variable in category

As I cannot create a synthesized property in a Category in Objective-C, I do not know how to optimize the following code:

@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

The test-method is called multiple times on runtime and I'm doing a lot of stuff to calculate the result. Normally using a synthesized property I store the value in a IVar _test the first time the method is called, and just returning this IVar next time. How can I optimized the above code?

76881 次浏览

@ loarian 的方法可以工作在 < sub > (注意: 答案现在已删除) 上,但是你只有一个存储槽。因此,如果您想在多个实例上使用它,并让每个实例计算一个不同的值,那么它将不会工作。

幸运的是,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

. h-file

@interface NSObject (LaserUnicorn)


@property (nonatomic, strong) LaserUnicorn *laserUnicorn;


@end

. m-file

#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

不使用 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)中使用)

给出的答案工作得很好,我的建议只是它的一个扩展,可以避免编写太多的样板代码。

为了避免重复编写类别属性的 getter 和 setter 方法,这个答案引入了宏。此外,这些宏还可以简化基元类型属性(如 intBOOL)的使用。

没有宏的传统方法

传统上定义类别属性,如

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

然后,您需要使用 相关物体去拿选择器作为键(看原始答案)实现一个 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

Following this pattern, you can simply add more macros to also support signed int, BOOL, etc...

限制

  1. 默认情况下,所有宏都使用 OBJC_ASSOCIATION_RETAIN_NONATOMIC

  2. 应用程序代码这样的 IDE 当前在重构属性的名称时不能识别 setter 的名称。你需要自己重命名它。

只需使用 Libextobjc库:

H-file:

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

M-file:

#import <extobjc.h>
@implementation MyClass (Variant)


@synthesizeAssociation (MyClass, test);


@end

更多关于@synizeAssociation

只在 iOS9上测试过 示例: 向 UINavigationBar (类别)添加 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