关联引用

关联引用允许开发者为任何对象附着键值数据

这种能力有很多用法,一种常见的用法是:

1)让分类为属性添加方法。

考虑 Person 类这个例子,假设你要用分类添加一个新属性,叫做 emailAddress。可能其他程序也用到了 Person,有时候需要电子邮箱地址,有时候不需要,分类就是很好的解决方案,可以避免在不需要的时候开销。或者 Person 不是你的,而维护者没有为你添加这个属性。不管哪种情况,你要怎么解决这个问题呢?

首先,这里有一个基本的 Person 类:

@interface Person : NSObject
@property(nonatomic, readwrite, copy) NSString *name;
@end

@implementation Person
@end
现在在分类中关联引用添加一个新属性 emailAddress 。

#import <objc/runtime.h>
@interface Person(EmailAddress)
@property(nonatomic, readwrite, copy) NSString *emailAddress;
@end


@implementation Person(EmailAddress)
static char emailAddressKey;

-(NSString *)emailAddress {
    return objc_getAssociatedObject(self, &emailAddressKey);
}

-(void)setEmailAddress:(NSString *)emailAddress {
    objc_setAssociatedObject(self, &emailAddressKey, emailAddress, OBJC_ASSOCIATION_COPY);
}

@end
注意,关联引用基于键的内存地址,而不是值的。emailAddressKey 中存在着什么并不重要,只要是唯一的不变的地址就可以了。这也是一般会用未赋值的 static char 变量作为键的原因。

2)关联引用有良好的内存管理,能根据传递给 objc_setAssociatedObject 的参数正确的处理 copy、assign 和 retain 等语义。当相关对象被销毁时关联引用会被释放。这个事实意味着可以用关联引用来追踪另一个对象何时被销毁。比如:

const char kWatcherKey;
@interface WatcherKey : NSObject
@end

#import <objc/runtime.h>
@implementation WatcherKey
-(void)dealloc {
    NSLog(@"HEY! The thing I was watching is going away!");
}
@end

......
NSObject *something = [NSObject new];
objc_setAssociatedObject(something, &kWatcherKey, [Watcher new], OBJC_ASSOCIATION_RETAIN)
这种技术对调试很有用,不过也可以用做非调试目的,比如执行清理工作。

3)关联引用是给警告框或者控件附着相关对象的好办法。比如说,你可以给警告框附着一个“表示对象”,如下代码所示:

#import <objc/runtime.h>

@implementation ViewController

static const char kRepresentedObject;

- (IBAction)doSomething:(id)sender {
  UIAlertView *alert = [[UIAlertView alloc]
                        initWithTitle:@"Alert" message:nil
                        delegate:self
                        cancelButtonTitle:@"OK"
                        otherButtonTitles:nil];
  objc_setAssociatedObject(alert, &kRepresentedObject, 
                           sender,
                           OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  [alert show];
  
}

现在,如果警告框被关闭了,你就可能知道原因了。

- (void)alertView:(UIAlertView *)alertView 
clickedButtonAtIndex:(NSInteger)buttonIndex {
  UIButton *sender = objc_getAssociatedObject(alertView, 
                                              &kRepresentedObject);
  self.buttonLabel.text = [[sender titleLabel] text];
}
很多程序用调用者的实力变量处理这种任务,但是关联引用清晰很多,也简单很多。对熟悉 Mac 开发的人来说,这段代码类似于 representedObject ,但是更加灵活。

最后,说一下关联引用的局限(或者其他任何通过分类添加数据的方法)是无法集成 encodeWithCoder: ,所以很难通过分类序列化对象。


拓展阅读:

全局返回手势 FDFullscreenPopGesture ,里面就是用了关联引用,同时也可以学到 OC 的一些黑魔法,Nice ~

你可能感兴趣的:(ios,关联引用)