[英]Objective-C: Property / instance variable in category
由于无法在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。 如何优化上面的代码?
.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
@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
给定的答案非常有效,我的建议只是对其的扩展,避免了编写过多的样板代码。
为了避免为类别属性重复编写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
为给定属性添加吸气剂和吸气剂。 只读或只写属性将分别使用CATEGORY_PROPERTY_GET
和CATEGORY_PROPERTY_SET
宏。
原始类型需要更多注意
由于原始类型不是对象,因此上述宏包含使用unsigned int
作为属性类型的示例。 通过将整数值包装到NSNumber
对象中来实现。 因此,它的用法类似于前面的示例:
@interface ...
@property unsigned int value;
@end
@implementation ...
CATEGORY_PROPERTY_GET_SET_UINT(value, setValue:)
@end
按照这种模式,您可以简单地添加更多宏以支持signed int
, BOOL
等。
局限性
默认情况下,所有宏都使用OBJC_ASSOCIATION_RETAIN_NONATOMIC
。
重构属性名称时,像App Code这样的IDE当前无法识别设置者的名称。 您需要自己重命名。
只需使用libextobjc库:
h文件:
@interface MyClass (Variant)
@property (nonatomic, strong) NSString *test;
@end
m文件:
#import <extobjc.h>
@implementation MyClass (Variant)
@synthesizeAssociation (MyClass, test);
@end
仅在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
另一个可能不使用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)中使用)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.