阅读量:0
背景:在面试过程中被问到如果两个对象已经发生循环引用了,该如何将他们剪断,在运行态的时候。
由于这个场景比较抽象,我理解面试官是希望我通过运行时的方法和方式来解决循环引用。
解决方案一:
重写setter用关联对象来实现weak引用。由于objc_setAssociatedObject中的策略不支持weak修饰对象属性。如果我们可以借助中间类或者block持有弱引用对象来实现。
- (void)setMyObject:(id)myObject { id __weak weakObject = myObject; id (^block)(void) = ^ { return weakObject; }; objc_setAssociatedObject(self, @selector(myObject), block, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (id)myObject { id (^block)(void) = objc_getAssociatedObject(self, _cmd); return block(); }
解决方案二:
还是重写setter通过关联对象实现弱引用,但是弱引用的实现不是通过中间对象的方式,而是通过runtime运行时重写value对象的子类的delloc方法,在这个方法中将关联对象的value设置成ni;
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); } - (id)anthorObj { return objc_getAssociatedObject(self, _cmd); } - (void)setAnthorObj:(id)anthorObj { weak_setAssociatedObject(self, @selector(anthorObj), anthorObj); }
测试代码如下:
Engine * a = [[Engine alloc] init]; { Tire * b = [[Tire alloc] init]; Tire * c = [[Tire alloc] init]; a.myObject = b; a.anthorObj = c; NSLog(@"myObject%@", a.myObject); NSLog(@"anthorObject%@", a.anthorObj); } NSLog(@"myObject outer%@", a.myObject); NSLog(@"anthorObject outer%@", a.anthorObj);
可以看到过了b、c对象出了作用域后,a.myObject以及a.anthorObject被改成nil.