有时需要在对象中存放相关信息,这时我们通常会从所属的类中继承一个字类,然后改用这个字类对象。然而并非所有情况下都能这样做,有时候类的实例可能是由某种机制创建的,而开发者无法令这种机制创建出自己所写的字类实例。OC中有一种很强大的特性可以解决此问题,就是“关联对象”。
可以通过下列方法管理关联对象:
void objc_setAssociatedObject (id object, void*key, id value, objc_AssociationPolicy policy)
此方法以给定的键和策略为某对象设置关联值。
id objc_getAssociatedObject(id object, void *key)
此方法根据给定的键从某对象中获取相应的关联对象值。
void objc_removeAssociatedObjects(id object)
此方法移除指定对象的全部关联对象。
用法举例:
1)创建UIAlertView
- (void)askUserAQuestion { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"question" message:nil delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil]; [alert show]; } #pragma mark - <UIAlertViewDelegate> - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex == 0) { // do ... }else { // do ... } }
但是有时候我们想让创建警告视图与处理操作结果的代码都放在一起,这样更易懂我们也不需要在两部分代码之间来回游走,即可明白视图的用处。所以我们采用关联对象修改一下:
static void *alertViewKey = "alertViewKey"; - (void)askUserAQuestion { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"question" message:nil delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil]; void (^block)(NSInteger) = ^(NSInteger buttonIndex) { if (buttonIndex == 0) { // do something }else { // do something } }; objc_setAssociatedObject(alert, alertViewKey, block, OBJC_ASSOCIATION_RETAIN_NONATOMIC); [alert show]; } #pragma mark - <UIAlertViewDelegate> - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { void (^block)(NSInteger) = objc_getAssociatedObject(alertView, alertViewKey); block(buttonIndex); }
需要注意:块可能要捕获某些变量,这也许会造成循环引用。其实例子相当于给alertView增加了个block属性,同理也可以用关联对象给分类添加属性:
@interface NSObject (WKCategoryProperty)
@property (nonatomic, copy) NSString *categoryProperty;
@end
#import "NSObject+WKCategoryProperty.h"
#import <objc/runtime.h>
static void *categoryPropertyKey = "categoryPropertyKey";
@implementation NSObject (WKCategoryProperty)
- (NSString *)categoryProperty {
return objc_getAssociatedObject(self, categoryPropertyKey);
}
- (void)setCategoryProperty:(NSString *)categoryProperty {
objc_setAssociatedObject(self, categoryPropertyKey, categoryProperty, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
注意:
@property (nonatomic, copy) NSString *property;
这行代码实际上会做三件事情:
1)生成实例变量property
2)生成get方法
3)生成set方法
这些都是编译器帮助生成的,虽然我们看不到,但它确实就在那里。哪为什么分类中不能使用呢,@property` 其实有元编程的思想,它能够为我们自动生成实例变量以及存取方法,而这三者构成了属性这个类似于语法糖的概念,为我们提供了更便利的点语法来访问属性,因为分类的实例变量的布局已经固定,使用 @property
已经无法向固定的布局中添加新的实例变量(这样做可能会覆盖子类的实例变量),所以我们需要使用关联对象以及两个方法来模拟构成属性的三个要素。
总结:
可以通过“关联对象”机制来把两个对象连起来。
定义关联对象时可指定内存管理语义,用以模仿定义属性时所采用的“拥有关系”与“非拥有关系”。
只有在其他做法不可行时才应选用关联对象,因为这种做法通常会引入难以查找的bug。
demo: