Objective-C:类别中的属性/实例变量

时间:2022-08-07 10:47:11

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

由于我无法在Objective-C中创建一个类中的合成属性,我不知道如何优化以下代码:

@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?

测试方法在运行时被多次调用,我正在做大量的工作来计算结果。通常,在第一次调用方法时,我使用一个合成属性将值存储在IVar _test中,并在下一次返回这个IVar。如何优化上述代码?

6 个解决方案

#1


116  

@lorean's method will work (note: answer is now deleted), but you'd only have a single storage slot. So if you wanted to use this on multiple instances and have each instance compute a distinct value, it wouldn't work.

@lorean的方法将会工作(注意:答案现在被删除),但是你只有一个存储槽。如果你想在多个实例中使用它并让每个实例计算一个不同的值,它就不能工作了。

Fortunately, the Objective-C runtime has this thing called Associated Objects that can do exactly what you're wanting:

幸运的是,Objective-C运行时有一个叫做关联对象的东西,它可以做你想做的事情:

#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

#2


154  

.h-file

头文件里

@interface NSObject (LaserUnicorn)

@property (nonatomic, strong) LaserUnicorn *laserUnicorn;

@end

.m-file

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

Just like a normal property - accessible with dot-notation

就像一个普通的属性-可以用点符号表示

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

Easier syntax

简单的语法

Alternatively you could use @selector(nameOfGetter) instead of creating a static pointer key like so:

或者,也可以使用@selector(nameOfGetter),而不是像这样创建静态指针键:

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

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

For more details see https://*.com/a/16020927/202451

有关详细信息,请参见https://*.com/a/16020927/202451

#3


26  

The given answer works great and my proposal is just an extension to it that avoids writing too much boilerplate code.

给定的答案非常有效,我的建议只是对它的扩展,避免编写太多的样板代码。

In order to avoid writing repeatedly getter and setter methods for category properties this answer introduces macros. Additionally these macros ease the use of primitive type properties such as int or BOOL.

为了避免重复编写类别属性的getter和setter方法,这个答案引入了宏。此外,这些宏可以方便地使用原始类型属性,如int或BOOL。

Traditional approach without macros

传统方法没有宏

Traditionally you define a category property like

通常您定义一个类别属性,如

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

Then you need to implement a getter and setter method using an associated object and the get selector as the key (see original answer):

然后需要使用关联对象和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

My suggested approach

我建议的方法

Now, using a macro you will write instead:

现在,使用一个宏,你可以这样写:

@implementation MyClass (Category)

CATEGORY_PROPERTY_GET_SET(NSString*, text, setText:)

@end

The macros are defined as following:

宏的定义如下:

#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)

The macro CATEGORY_PROPERTY_GET_SET adds a getter and setter for the given property. Read-only or write-only properties will use the CATEGORY_PROPERTY_GET and CATEGORY_PROPERTY_SET macro respectively.

宏CATEGORY_PROPERTY_GET_SET为给定的属性添加了一个getter和setter。只读属性或只写属性将分别使用CATEGORY_PROPERTY_GET和CATEGORY_PROPERTY_SET宏。

Primitive types need a little more attention

原始类型需要更多的关注

As primitive types are no objects the above macros contain an example for using unsigned int as the property's type. It does so by wrapping the integer value into a NSNumber object. So its usage is analog to the previous example:

由于基本类型是没有对象的,上面的宏包含一个使用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...

按照这种模式,您可以简单地添加更多的宏来支持带符号int、BOOL等。

Limitations

限制

  1. All macros are using OBJC_ASSOCIATION_RETAIN_NONATOMIC by default.

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

  2. IDEs like App Code do currently not recognize the setter's name when refactoring the property's name. You would need to rename it by yourself.

    类似于应用程序代码的ide当前在重构属性名时不能识别setter的名称。您需要自己重命名它。

#4


7  

Just use libextobjc library:

只是使用libextobjc库:

h-file:

h文件:

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

m-file:

m文件:

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

@synthesizeAssociation (MyClass, test);

@end

More about @synthesizeAssociation

更多关于@synthesizeAssociation

#5


3  

Tested only with iOS 9 Example: Adding an UIView property to UINavigationBar (Category)

仅在ios9示例中测试:将UIView属性添加到UINavigationBar(类别)

UINavigationBar+Helper.h

UINavigationBar + Helper.h

#import <UIKit/UIKit.h>

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

UINavigationBar+Helper.m

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

#6


-2  

Another possible solution, perhaps easier, which doesn't use Associated Objects is to declare a variable in the category implementation file as follows:

另一个不使用关联对象的可能的解决方案是在类别实现文件中声明一个变量如下所示:

@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

The downside of this sort of implementation is that the object doesn't function as an instance variable, but rather as a class variable. Also, property attributes can't be assigned(such as used in Associated Objects like OBJC_ASSOCIATION_RETAIN_NONATOMIC)

这种实现的缺点是,对象不是作为实例变量,而是作为类变量。此外,属性属性也不能被分配(例如在OBJC_ASSOCIATION_RETAIN_NONATOMIC等相关对象中使用)

#1


116  

@lorean's method will work (note: answer is now deleted), but you'd only have a single storage slot. So if you wanted to use this on multiple instances and have each instance compute a distinct value, it wouldn't work.

@lorean的方法将会工作(注意:答案现在被删除),但是你只有一个存储槽。如果你想在多个实例中使用它并让每个实例计算一个不同的值,它就不能工作了。

Fortunately, the Objective-C runtime has this thing called Associated Objects that can do exactly what you're wanting:

幸运的是,Objective-C运行时有一个叫做关联对象的东西,它可以做你想做的事情:

#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

#2


154  

.h-file

头文件里

@interface NSObject (LaserUnicorn)

@property (nonatomic, strong) LaserUnicorn *laserUnicorn;

@end

.m-file

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

Just like a normal property - accessible with dot-notation

就像一个普通的属性-可以用点符号表示

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

Easier syntax

简单的语法

Alternatively you could use @selector(nameOfGetter) instead of creating a static pointer key like so:

或者,也可以使用@selector(nameOfGetter),而不是像这样创建静态指针键:

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

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

For more details see https://*.com/a/16020927/202451

有关详细信息,请参见https://*.com/a/16020927/202451

#3


26  

The given answer works great and my proposal is just an extension to it that avoids writing too much boilerplate code.

给定的答案非常有效,我的建议只是对它的扩展,避免编写太多的样板代码。

In order to avoid writing repeatedly getter and setter methods for category properties this answer introduces macros. Additionally these macros ease the use of primitive type properties such as int or BOOL.

为了避免重复编写类别属性的getter和setter方法,这个答案引入了宏。此外,这些宏可以方便地使用原始类型属性,如int或BOOL。

Traditional approach without macros

传统方法没有宏

Traditionally you define a category property like

通常您定义一个类别属性,如

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

Then you need to implement a getter and setter method using an associated object and the get selector as the key (see original answer):

然后需要使用关联对象和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

My suggested approach

我建议的方法

Now, using a macro you will write instead:

现在,使用一个宏,你可以这样写:

@implementation MyClass (Category)

CATEGORY_PROPERTY_GET_SET(NSString*, text, setText:)

@end

The macros are defined as following:

宏的定义如下:

#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)

The macro CATEGORY_PROPERTY_GET_SET adds a getter and setter for the given property. Read-only or write-only properties will use the CATEGORY_PROPERTY_GET and CATEGORY_PROPERTY_SET macro respectively.

宏CATEGORY_PROPERTY_GET_SET为给定的属性添加了一个getter和setter。只读属性或只写属性将分别使用CATEGORY_PROPERTY_GET和CATEGORY_PROPERTY_SET宏。

Primitive types need a little more attention

原始类型需要更多的关注

As primitive types are no objects the above macros contain an example for using unsigned int as the property's type. It does so by wrapping the integer value into a NSNumber object. So its usage is analog to the previous example:

由于基本类型是没有对象的,上面的宏包含一个使用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...

按照这种模式,您可以简单地添加更多的宏来支持带符号int、BOOL等。

Limitations

限制

  1. All macros are using OBJC_ASSOCIATION_RETAIN_NONATOMIC by default.

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

  2. IDEs like App Code do currently not recognize the setter's name when refactoring the property's name. You would need to rename it by yourself.

    类似于应用程序代码的ide当前在重构属性名时不能识别setter的名称。您需要自己重命名它。

#4


7  

Just use libextobjc library:

只是使用libextobjc库:

h-file:

h文件:

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

m-file:

m文件:

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

@synthesizeAssociation (MyClass, test);

@end

More about @synthesizeAssociation

更多关于@synthesizeAssociation

#5


3  

Tested only with iOS 9 Example: Adding an UIView property to UINavigationBar (Category)

仅在ios9示例中测试:将UIView属性添加到UINavigationBar(类别)

UINavigationBar+Helper.h

UINavigationBar + Helper.h

#import <UIKit/UIKit.h>

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

UINavigationBar+Helper.m

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

#6


-2  

Another possible solution, perhaps easier, which doesn't use Associated Objects is to declare a variable in the category implementation file as follows:

另一个不使用关联对象的可能的解决方案是在类别实现文件中声明一个变量如下所示:

@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

The downside of this sort of implementation is that the object doesn't function as an instance variable, but rather as a class variable. Also, property attributes can't be assigned(such as used in Associated Objects like OBJC_ASSOCIATION_RETAIN_NONATOMIC)

这种实现的缺点是,对象不是作为实例变量,而是作为类变量。此外,属性属性也不能被分配(例如在OBJC_ASSOCIATION_RETAIN_NONATOMIC等相关对象中使用)