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


122

由于无法在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。如何优化上面的代码?


2
为什么不按常规方式仅将属性而不是类别添加到MyClass基类中呢?更进一步,请在后台执行繁重的工作,并在流程完成时让该流程触发通知或为MyClass调用某个处理程序。
杰里米

4
MyClass是从Core Data生成的类。如果我在生成的类中使用我的自定义对象代码,但是如果我从核心数据中重新生成该类,它将消失。因此,我正在使用类别。
dhrm 2012年

1
也许接受最适合标题的问题?(“类别中的属性”)
hfossli 2014年

为什么不创建子类呢?
朱Scott(Scott Zhu)

Answers:


124

@lorean的方法将起作用(注意:答案现在已删除),但是您只有一个存储插槽。因此,如果您想在多个实例上使用它,并让每个实例计算一个不同的值,则它将无法正常工作。

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


5
@DaveDeLong谢谢您的解决方案!不是很漂亮,但是可以用:)
dhrm

42
“不是很漂亮”?这就是Objective-C的美!;)
Dave DeLong 2012年

6
好答案!你甚至可以摆脱使用静态变量@selector(test)作为重点,如下解释:stackoverflow.com/questions/16020918/...
加布里埃尔Petronella

1
@HotFudgeSunday -觉得它在SWIFT工作: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字符串作为键,然后将该字符串复制到内存中的另一个位置,并尝试通过将指针传递给复制的字符串作为键来尝试访问关联的引用,则它将无法正常工作。
罗杰斯先生

4
您真的不需要@dynamic objectTag;@dynamic意味着将在其他地方生成setter和getter,但在这种情况下,它们将在此处实现。
IluTov

@NSAddict正确!固定!
hfossli 2013年

1
释放laserUnicorns进行手动内存管理怎么样?这是内存泄漏吗?
曼尼2014年


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 intBOOL等...

局限性

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

  2. 重构属性名称时,像App Code这样的IDE 当前无法识别设置者的名称。您需要自己重命名。


1
不要忘记#import <objc/runtime.h>在.m文件类别中。编译时错误:在C99中,函数'objc_getAssociatedObject'的隐式声明无效stackoverflow.com/questions/9408934/...
Sathe_Nagaraja


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

-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 2014年

1
真??你知道你在做什么吗?您创建了一对getter / setter方法来访问一个与文件无关的内部变量,但会获得一个类对象,即,如果您分配了两个UIAlertView类对象,则它们的对象值是相同的!
Itachi 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.