关联引用概念
利用 OC 语言的动态性,借助运行时(runtime)的功能,我们可以为已存在的实例对象增加实例变量,这个功能叫做关联引用。
添加、检索和断开关联
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,id _Nullable value, objc_AssociationPolicy policy)
该方法为对象 object 添加以 key 指定的地址作为关键字、以value为值的关联引用,第四个参数policy指定关联引用的存储策略。
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key) OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
返回 object 以 Key 为关键字的关联对象,如果没有关联对象,则返回 nil
objc_removeAssociatedObjects(id _Nonnull object) OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
断开关联
存储策略
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0, /*弱引用对象保存对象*/ OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /*强引用对象保存对象,非原子性*/ OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /*复制一份原对象,非原子性*/ OBJC_ASSOCIATION_RETAIN = 01401, /*引用对象保存对象,默认原子性,多线程安全 */ OBJC_ASSOCIATION_COPY = 01403 /*复制一份原对象,默认原子性,多线程安全*/};
实例
假设我们为 NSArray 增加了一个新的随机取元素的方法,并且取得的元素不可以连续相同,我们利用范畴(category)为 NSArray 扩展一个方法。
NSArray+Random.h
#import@interface NSArray (Random)- (id)anyOne;@end
NSArray+Random.m
#import "NSArray+Random.h"#import@implementation NSArray (Random)static char prevKey;- (id)anyOne { id item; NSUInteger count = [self count]; if (count == 0) { return nil; }else if(count == 1){ return [self lastObject]; }else{ id prev = objc_getAssociatedObject(self, &prevKey);//获取关联对象所引用的值,初次使用返回 nil NSUInteger index = random()%count; item = self[index]; if (item == prev) {//索引相同情况下,取下一个元素,若该索引是数组最后一个,则取第一个值 if (++index >= count) { index = 0; } item = self[index]; } printf("item:%s,prevItem:%s\n",[item UTF8String],[prev UTF8String]); } objc_setAssociatedObject(self, &prevKey, item, OBJC_ASSOCIATION_RETAIN);//存储最后返回的对象 return item;}
main.m
#import#import "AppDelegate.h"#import "NSArray+Random.h"int main(int argc, char * argv[]) { id arr1 = @[@"1",@"2",@"3",@"4",@"5",@"6",@"7"]; id arr2 = @[@"a",@"b",@"c",@"d",@"e",@"f",@"g"]; for (int i=0; i<15; i++) { printf("arr1:%s,arr2:%s\n",[[arr1 anyOne] UTF8String], [[arr2 anyOne] UTF8String]); } @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}
运行结果
item:2,prevItem:(null)item:e,prevItem:(null)arr1:2,arr2:eitem:3,prevItem:2item:f,prevItem:earr1:3,arr2:fitem:2,prevItem:3item:d,prevItem:farr1:2,arr2:ditem:4,prevItem:2item:c,prevItem:darr1:4,arr2:citem:2,prevItem:4item:d,prevItem:carr1:2,arr2:ditem:3,prevItem:2item:f,prevItem:darr1:3,arr2:fitem:7,prevItem:3item:e,prevItem:farr1:7,arr2:eitem:1,prevItem:7item:a,prevItem:earr1:1,arr2:a
结语
综合使用关联引用和范畴,可以大大增强 OC 编程的灵活性,但也不能滥用,会导致程序不好理解。