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

dhr*_*hrm 118 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
Run Code Online (Sandbox Code Playgroud)

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

hfo*_*sli 170

.H文件

@interface NSObject (LaserUnicorn)

@property (nonatomic, strong) LaserUnicorn *laserUnicorn;

@end
Run Code Online (Sandbox Code Playgroud)

.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
Run Code Online (Sandbox Code Playgroud)

就像一个普通的财产 - 可用点符号访问

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

语法更简单

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

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

- (void)setLaserUnicorn:(LaserUnicorn *)unicorn {
    objc_setAssociatedObject(self, @selector(laserUnicorn), unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
}
Run Code Online (Sandbox Code Playgroud)

有关更多详细信息,请参阅/sf/answers/1121464921/

  • 好文章.需要注意的一件事是文章中的更新.__Update 2011年12月22日:重要的是要注意关联的关键是void指针void*key,而不是字符串.这意味着在检索关联引用时,必须将完全相同的指针传递给运行时.如果您使用C字符串作为键,它将无法按预期工作,然后将字符串复制到内存中的另一个位置,并尝试通过将指针作为键传递给复制的字符串来访问关联的引用.__ (4认同)
  • 你真的不需要`@dynamic objectTag;`.`@ dynamic`表示setter和getter将在其他地方生成,但在这种情况下,它们就在这里实现. (4认同)

Dav*_*ong 123

@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
Run Code Online (Sandbox Code Playgroud)

  • "不是很漂亮"?这就是Objective-C的美丽!;) (42认同)
  • 很棒的答案!您甚至可以使用`@selector(test)`作为键来摆脱静态变量,如下所述:http://stackoverflow.com/questions/16020918/avoid-extra-static-variables-for-associated-objects-按键/ 16020927#16020927 (6认同)
  • @DaveDeLong感谢您的解决方案!不是很漂亮,但它的工作:) (5认同)

Lar*_*erg 31

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

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

没有宏的传统方法

传统上,您可以定义类别属性

@interface MyClass (Category)
@property (strong, nonatomic) NSString *text;
@end
Run Code Online (Sandbox Code Playgroud)

然后,您需要使用关联对象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
Run Code Online (Sandbox Code Playgroud)

我建议的做法

现在,使用宏,你会写:

@implementation MyClass (Category)

CATEGORY_PROPERTY_GET_SET(NSString*, text, setText:)

@end
Run Code Online (Sandbox Code Playgroud)

宏定义如下:

#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)
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

在此之后的图案,你可以简单地添加多个宏也支持signed int,BOOL等...

限制

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

  2. App Code这样的IDE 在重构属性名称时当前无法识别setter的名称.您需要自己重命名.


Man*_*lan 7

只需使用libextobjc库:

h文件:

@interface MyClass (Variant)
@property (nonatomic, strong) NSString *test;
@end
Run Code Online (Sandbox Code Playgroud)

m文件:

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

@synthesizeAssociation (MyClass, test);

@end
Run Code Online (Sandbox Code Playgroud)

有关@synthesizeAssociation的更多信息