关联引用概念
利用 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 <Foundation/Foundation.h>
@interface NSArray (Random)
- (id)anyOne;
@end
NSArray+Random.m
#import "NSArray+Random.h"
#import <objc/runtime.h>
@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 <UIKit/UIKit.h>
#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:e
item:3,prevItem:2
item:f,prevItem:e
arr1:3,arr2:f
item:2,prevItem:3
item:d,prevItem:f
arr1:2,arr2:d
item:4,prevItem:2
item:c,prevItem:d
arr1:4,arr2:c
item:2,prevItem:4
item:d,prevItem:c
arr1:2,arr2:d
item:3,prevItem:2
item:f,prevItem:d
arr1:3,arr2:f
item:7,prevItem:3
item:e,prevItem:f
arr1:7,arr2:e
item:1,prevItem:7
item:a,prevItem:e
arr1:1,arr2:a
结语
综合使用关联引用和范畴,可以大大增强 OC 编程的灵活性,但也不能滥用,会导致程序不好理解。