关联对象实现weak属性
通过关联对象objc_setAssociatedObject中的策略policy可知,并不支持使用weak修饰对象属性:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0, //assign OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //strong, nonatomic OBJC_ASSOCIATION_COPY_NONATOMIC = 3, //copy, nonatomic OBJC_ASSOCIATION_RETAIN = 01401, //strong, atomic OBJC_ASSOCIATION_COPY = 01403 //copy, atomic };
思考:能否用assign实现?
weak和assign的区别如下:
- assign:对应的所有权类型为__unsafe_unretained,当修饰对象的时候,修饰的指针指向该对象,不会去持有该对象,也不会使retainCount +1,而在指向的对象被释放时,依然指向原来的对象地址,不会被自动置为nil,所以造成了野指针,是不安全的;
- weak:弱引用,不会影响对象的释放,而当对象被释放时(引用计数为0),所有指向它的弱引用都会自定被置为nil,防止野指针,之后再向该对象发消息也不会崩溃,weak是安全的;
看以下测试代码,使用policy为OBJC_ASSOCIATION_ASSIGN的策略,会发生什么样的情况?
//定义Person类 @interface Person : NSObject @end @implementation Person - (void)dealloc { NSLog(@"Person dealloc"); } @end @interface Person (Test) //在分类中声明UIViewController属性,用assign修饰 @property(assign, nonatomic) UIViewController *viewController; @end @implementation Person (Test) - (void)setViewController:(UIViewController *)viewController { //利用objc_setAssociatedObject设置值,policy为OBJC_ASSOCIATION_ASSIGN objc_setAssociatedObject(self, @selector(viewController), viewController, OBJC_ASSOCIATION_ASSIGN); } - (UIViewController *)viewController {//取值 return objc_getAssociatedObject(self, _cmd); } @end
调用并运行:
使用assign修饰对象,当离开作用域后,产生野指针访问Crash(如图),如何避免这个问题?
实现方式一
借助中间类,利用OBJC_ASSOCIATION_RETAIN_NONATOMIC + weak来实现;创建中间类:
@interface WeakObjWrapper : NSObject @property(weak, nonatomic) id weakObj; @end @implementation WeakObjWrapper - (instancetype)initWithWeakObject:(id)weakObj { if (self = [super init]) { _weakObj = weakObj; } return self; } @end
实现属性的setter和getter:
@interface Person (Test) @property(weak, nonatomic) UIViewController *viewController; @end @implementation Person (Test) - (void)setViewController:(UIViewController *)viewController { WeakObjWrapper *warpper = objc_getAssociatedObject(self, @selector(viewController)); //用warpper保存传递进来的值 if (!warpper) {//warpper不存在则创建 warpper = [[WeakObjWrapper alloc] initWithWeakObject:viewController]; }else {//已经存在直接赋值 warpper.weakObj = viewController; } //保存的实际上是warpper对象 objc_setAssociatedObject(self, @selector(viewController), warpper, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (UIViewController *)viewController { //获取到warpper WeakObjWrapper *warpper = objc_getAssociatedObject(self, _cmd); //取出warpper中的值 return warpper.weakObj; } @end
测试调用:
int main(int argc, char * argv[]) { { Person *person = [[Person alloc] init]; { UIViewController *viewController = [UIViewController new]; person.viewController = viewController; NSLog(@"in - %@", person.viewController); //打印结果: in - <UIViewController: 0x101204370> } NSLog(@"end - %@", person.viewController); } return 0; }
运行结果:
in - <UIViewController: 0x7fea1a404080> end - (null)
objc_setAssociatedObject实际上存储的是WeakObjWrapper对象,对WeakObjWrapper对象产生强引用,WeakObjWrapper对象内部弱持有传递进去的值,保证在对象释放的时候,自动把值设置为nil,避免了崩溃;
实现方式二
继续使用OBJC_ASSOCIATION_ASSIGN,利用runtime动态的创建传进去值的类的子类,在子类的dealloc中,将objc_setAssociatedObject中的value设置为nil,销毁并移除了关联对象,避免Crash,具体实现如下(具体使用已在注释中说明):
void weak_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value) { //派生一个子类,类名为WeakObjWrapper+value对应的类名 const char *clsName = [[NSString stringWithFormat:@"WeakObjWrapper%@", [value class]] UTF8String]; //获取派生的子类 Class childCls = objc_getClass(clsName); //如果子类不存在,利用runtime动态的创建一个子类 if (!childCls) { childCls = objc_allocateClassPair([value class], clsName, 0); objc_registerClassPair(childCls); } //注册dealloc方法SEL SEL sel = sel_registerName("dealloc"); //获取dealloc对应的类型编码 const char *deallocEncoding = method_getTypeEncoding(class_getInstanceMethod([value class], sel)); // 注意:内部持有value此处需要弱引用处理一下 __weak typeof(value) weakValue = value; // 创建一个指向在调用dealloc方法时调用指定block的函数指针 IMP deallocImp = imp_implementationWithBlock(^(id _childCls) { //在子类的dealloc方法中将value设置为nil,避免崩溃 objc_setAssociatedObject(object, key, nil, OBJC_ASSOCIATION_ASSIGN); //派生的子类的dealloc方法会被调用,父类的不再被调用,故在此处调用一下父类的 ((void (*)(id, SEL))(void *)objc_msgSend)(weakValue, sel); }); //给子类添加dealloc方法 class_addMethod(childCls, sel, deallocImp, deallocEncoding); //将value对应的isa指向子类 object_setClass(value, childCls); //设置关联对象 objc_setAssociatedObject(object, key, value, OBJC_ASSOCIATION_ASSIGN); }
注意:在派生的子类,添加的实现dealloc的方法中,重新调用一下父类的dealloc保证原有的类的释放关系不被破坏;调用(实现属性的getter和setter):
@interface Person (Test) @property(assign, nonatomic) UIViewController *viewController; @end @implementation Person (Test) - (void)setViewController:(UIViewController *)viewController { weak_setAssociatedObject(self, @selector(viewController), viewController); } - (UIViewController *)viewController { return objc_getAssociatedObject(self, _cmd); } @end
测试调用:
int main(int argc, char * argv[]) { { Person *person = [[Person alloc] init]; { UIViewController *viewController = [UIViewController new]; person.viewController = viewController; NSLog(@"in - %@", person.viewController); //打印结果: in - <UIViewController: 0x101204370> } NSLog(@"end - %@", person.viewController); } getchar(); return 0; }
运行结果:
in - <UIViewController: 0x7ff113704080> end - (null)
总结
关联对象中如何实现weak属性?
- 关联对象objc_setAssociatedObject中的策略policy可知,并不支持使用weak修饰对象属性;
- 可以借助中间类(OBJC_ASSOCIATION_RETAIN_NONATOMIC + weak)来实现;
- 也可以继续使用OBJC_ASSOCIATION_ASSIGN,利用runtime动态的创建传进去值的类的子类,在子类的dealloc中,将objc_setAssociatedObject中的value设置为nil,销毁并移除了关联对象,从而避免了Crash;
本文部分内容来可能来源于网络,发布的内容如果侵犯了您的权益,请联系我们尽快删除!