Objective-C中两对象循环引用:为何直接置nil无效,置属性为nil有效?
这个问题其实戳中了Objective-C强引用循环的核心——引用计数的变化到底发生在谁身上,咱们一步步拆解来看就清楚了:
先搞懂初始状态的引用关系
假设TestA的b属性和TestB的a属性都是强引用属性(这是循环引用的前提),咱们逐个看代码执行后的引用计数变化:
TestA *ta = [[TestA alloc] init];:TestA对象被创建,引用计数为1,同时变量ta作为强指针,牢牢“抓着”这个TestA对象(计数保持1)。TestB *tb = [[TestB alloc] init];:同理,TestB对象引用计数为1,变量tb抓着它。ta.b = tb;:TestA对象的b属性会再“抓”一次TestB,TestB的引用计数变成2(变量tb+ TestA的b属性)。tb.a = ta;:TestB对象的a属性再“抓”一次TestA,TestA的引用计数变成2(变量ta+ TestB的a属性)。
这时候两个对象形成了死循环:TestA被TestB抓着,TestB被TestA抓着,同时各自还被变量ta/tb抓着,谁也没法被销毁。
为什么ta = nil或tb = nil没用?
当你执行ta = nil,只是变量ta松开了对TestA对象的抓握——TestA的引用计数从2降到1,但TestB对象的a属性还死死抓着TestA呢!
同时,TestA对象还活着,它的b属性还抓着TestB,所以TestB的引用计数还是2(变量tb + TestA的b),根本降不到0。
同理,tb = nil也只是变量tb松开TestB,TestB计数从2到1,但TestA的b还抓着它,TestA的计数也还是2,两个对象都不会被释放,循环依然存在。
为什么ta.b = nil就有效?
当你执行ta.b = nil,是让TestA对象的b属性松开对TestB的抓握——TestB的引用计数直接从2降到1,现在只有变量tb还抓着TestB了。
接下来只要做这两步:
ta = nil:变量ta松开TestA,TestA的引用计数从2降到1(只剩TestB的a属性抓着它);tb = nil:变量tb松开TestB,TestB的引用计数降到0,TestB会被系统销毁。
关键来了:TestB被销毁的时候,它的所有属性都会被自动置nil(包括强属性a),这就相当于TestB的a属性也松开了对TestA的抓握,TestA的引用计数从1降到0,TestA也跟着被销毁——循环彻底被打破!
一句话总结
ta = nil只是解除了外部变量对对象的强引用,但对象内部的互相引用还在;而ta.b = nil是直接切断了对象之间的互相强引用链,让后续的变量释放能触发整个循环的销毁流程。
内容的提问来源于stack exchange,提问作者allentang

