我们在 iOS 开发中经常需要使用分类(Category),为已经存在的类添加属性的需求,但是使用 @property 并不能在分类中正确创建实例变量和存取方法。这时候就会用到关联对象。
分类中的 @property
@interface DKObject : NSObject
@property (nonatomic, strong) NSString *property;
@end
在使用上述代码时会做三件事:
- 生成带下划线的实例变量 _property
- 生成 getter 方法 - property
- 生成 setter 方法 - setProperty:
@implementation DKObject {
NSString *_property;
}
- (NSString *)property {
return _property;
}
- (void)setProperty:(NSString *)property {
_property = property;
}
@end
这些代码都是编译器为我们生成的,虽然你看不到它,但是它确实在这里,我们既然可以在类中使用 @property
生成一个属性,那么为什么在分类中不可以呢?
我们来做一个小实验:创建一个 DKObject 的分类 Category,并添加一个属性 categoryProperty
:
@interface DKObject (Category)
@property (nonatomic, strong) NSString *categoryProperty;
@end
看起来还是很不错的,不过 Build 一下这个 Demo,会发现有这么一个警告:
在这里的警告告诉我们 categoryProperty 属性的存取方法
需要自己手动去实现,或者使用 @dynamic 在运行时实现这些方法。
换句话说,分类中的 @property 并没有为我们生成实例变量以及存取方法
,而需要我们手动实现。
使用关联对象
Q:我们为什么要使用关联对象?
A:因为在分类中 @property 并不会自动生成实例变量以及存取方法
,所以一般使用关联对象为已经存在的类添加『属性
』。
以下是与关联对象有关的 API,并在分类中实现一个伪属性:
#import "DKObject+Category.h"
#import
@implementation DKObject (Category)
- (NSString *)categoryProperty {
return objc_getAssociatedObject(self, _cmd);
}
- (void)setCategoryProperty:(NSString *)categoryProperty {
objc_setAssociatedObject(self, @selector(categoryProperty), categoryProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
这里的
_cmd
代指当前方法的选择子,也就是@selector(categoryProperty)
。
我们使用了两个方法 objc_getAssociatedObject
以及 objc_setAssociatedObject
来模拟『属性』的存取方法,而使用关联对象模拟实例变量。
在这里有必要解释两个问题:
- 为什么向方法中传入
@selector(categoryProperty)?
-
OBJC_ASSOCIATION_RETAIN_NONATOMIC
是干什么的?
关于第一个问题,我们需要看一下这两个方法的原型:
id objc_getAssociatedObject(id object, const void *key);
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
@selector(categoryProperty)
也就是参数中的key
,其实可以使用静态指针 static void *
类型的参数来代替,不过在这里,笔者强烈推荐使用 @selector(categoryProperty)
作为 key 传入。因为这种方法省略了声明参数的代码,并且能很好地保证 key 的唯一性
。
OBJC_ASSOCIATION_RETAIN_NONATOMIC
又是什么呢?如果我们使用 Command 加左键查看它的定义:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
从这里的注释我们能看到很多东西,也就是说不同的 objc_AssociationPolicy 对应了不通的属性修饰符:
objc_AssociationPolicy | modifier |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic, strong |
OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic, copy |
OBJC_ASSOCIATION_RETAIN | atomic, strong |
OBJC_ASSOCIATION_COPY | atomic, copy |
而我们在代码中实现的属性 categoryProperty 就相当于使用了 nonatomic 和 strong 修饰符。
在obj dealloc时候会调用object_dispose,检查有无关联对象,有的话_object_remove_assocations删除
更多:iOS面试题合集