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

时间:2012-01-04 19:54:01

标签: objective-c categories

由于我无法在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

测试方法在运行时被多次调用,我正在做很多事情来计算结果。通常使用合成属性我会在第一次调用方法时将值存储在IVar _test中,并且下次只返回此IVar。如何优化上述代码?

6 个答案:

答案 0 :(得分:165)

<强> .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

答案 1 :(得分:121)

@ 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

答案 2 :(得分:31)

给出的答案很有效,我的建议只是对它的扩展,避免编写过多的样板代码。

为了避免为类别属性重复编写getter和setter方法,这个答案引入了宏。此外,这些宏可以轻松使用原始类型属性,例如intBOOL

没有宏的传统方法

传统上,您定义类别属性,如

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

然后,您需要使用关联对象 get selector 作为键(see original answer)来实现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 intBOOL等...

<强>限制

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

  2. 应用代码等IDE在重构属性的名称时,目前无法识别setter的名称。您需要自己重命名。

答案 3 :(得分:7)

只需使用libextobjc库:

h文件:

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

m文件:

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

@synthesizeAssociation (MyClass, test);

@end

More about @synthesizeAssociation

答案 4 :(得分:3)

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

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

答案 5 :(得分:-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等关联对象中使用)