iOS weak的实现原理详解
1、先上原理
weak声明的指针指向实例对象的原理就是利用runtime底层维护着一个 SideTable 结构体来实现的weak指针的保存和移除,里面啥有如下图那些。
spinlock_t 是自旋锁
RefcountMap 是key为对象的地址,和value为引用计数的一个hash表
weak_table_t 是key为对象的地址,和value为weak指针的集合的一个hash表
添加: 多一个weak指针指向对象的话就会调用一次 objc_storeWeak 方法,里面会调用下图中的 weak_register_no_lock 方法往hash表中增加一个,RefcountMap 里对应的引用计数也会变更。
移除: 少一个weak指针指向对象的话里面会调用 weak_unregister_no_lock 方法往hash表中减少一个,RefcountMap 里对应的引用计数也会变更。
销毁: 对象销毁时,就会调用一次 objc_destructInstance 方法,里面会调用 weak_clear_no_lock 方法,会找到指向对象的所有weak指针调用 weak_entry_remove 方法执行remove操作,RefcountMap 里对应的引用计数也会清0。
2、代码角度分析原理
2.1、引用关系
2.1.1、手动实现了 Person 类的 dealloc 方法打印了一下,在大括号内用了 strong 指向实例对象,就会在 strongP 指针作用域结束释放对象打印,也就是在 222 后打印。
2.1.2、如果不用强引用指针指向 person 对象,用 weak 那么 -[Person dealloc] 应该打印在大括号内 111 和 222 之间,因为是弱引用所以 person 对象的作用域结束后释放了,weakP指针指向 nil,打印为 null。 image.png
2.1.3、如果是 unsafe_unretained ,-[Person dealloc] 打印也在大括号内 111 和 222 之间,但是打印unsafeP就会crash 报 EXC_BAD_ACCESS 坏内存野指针了,0x2809044c0 地址上的内存在大括号结束的时候就已经释放了,但是指针不会至nil,打印还打印这个地址的内存,所以出现了这个crash,所以说unsafe_unretained是不安全的。
2.2、weak指针的增加
通过断点看汇编代码【Debug > Debug Workflow > Always Show Disassembly】发现了 weakP = person; 的时候底层调用了 objc_storeWeak 方法验证了上面的原理,有兴趣的可以跟读一下源码看看obj4源码。
2.3、对象释放- dealloc
当一个对象要释放时,会自动调用dealloc,接下的调用轨迹是这样的(两图片中间省略了一些调用图片)最后到 objc_destructInstance >> sidetable_clearDeallocating >> weak_clear_no_lock >> weak_entry_remove 就移除了所有弱引用指针,完了也会调用 table.refcnts.erase(it); 清0引用计数,可以源码跟读一下,obj4源码。
- dealloc
- _objc_rootDealloc
- rootDealloc
- object_dispose
- objc_destructInstance、free