【问题标题】:Objective-C: Property / instance variable in categoryObjective-C:类别中的属性/实例变量
【发布时间】:2012-02-02 17:20:38
【问题描述】:

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

@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

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

【问题讨论】:

  • 为什么不做你通常做的事情,只是代替类别,将属性添加到 MyClass 基类?更进一步,在后台执行繁重的工作并让进程触发通知或在进程完成时为 MyClass 调用一些处理程序。
  • MyClass 是从 Core Data 生成的类。如果我将我的自定义对象代码放在生成的类中,如果我从我的核心数据重新生成类,它就会消失。因此,我使用了一个类别。
  • 也许接受最适合标题的问题? (“类别中的属性”)
  • 为什么不直接创建一个子类?

标签: objective-c categories


【解决方案1】:

.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

【讨论】:

  • 好文章。需要注意的一件事是文章中的更新。 2011 年 12 月 22 日更新:请务必注意,关联的键是 void 指针 void *key,而不是字符串。这意味着在检索关联引用时,您必须将完全相同的指针传递给运行时。如果您使用 C 字符串作为键,然后将字符串复制到内存中的另一个位置并尝试通过将指向复制字符串的指针作为键传递来访问关联的引用,它将无法按预期工作。跨度>
  • 你真的不需要@dynamic objectTag;@dynamic 表示 setter 和 getter 将在其他地方生成,但在这种情况下,它们就在这里实现。
  • @NSAddict 真的!固定!
  • laserUnicorns手动内存管理的dealloc怎么样?这是内存泄漏吗?
【解决方案2】:

@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

【讨论】:

【解决方案3】:

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

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

没有宏的传统方法

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

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

那么你需要实现一个getter和setter方法,使用一个关联对象get选择器作为键(see original answer):

#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 目前在重构属性名称时无法识别 setter 的名称。您需要自己重命名。

【讨论】:

  • 别忘了#import &lt;objc/runtime.h&gt;in category .m 文件,否则。编译时错误:函数'objc_getAssociatedObject'的隐式声明在C99中无效出现。 stackoverflow.com/questions/9408934/…
【解决方案4】:

只需使用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

【讨论】:

  • 用这个覆盖访问器的正确方法是什么(例如延迟加载属性)
【解决方案5】:

仅在 iOS 9 上测试 示例:将 UIView 属性添加到 UINavigationBar (Category)

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

【讨论】:

    【解决方案6】:

    另一个可能的解决方案,也许更简单,不使用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
    • 真的吗??你知道你在做什么吗?您创建了一对 getter/setter 方法来访问一个独立于文件但类对象的内部变量,也就是说,如果您分配两个 UIAlertView 类对象,它们的对象值是相同的!
    猜你喜欢
    • 2011-05-07
    • 2010-10-25
    • 2010-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-19
    • 2013-02-02
    相关资源
    最近更新 更多