- 小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
1. 散列表
散列表的本质是哈希表。散列表在手机上有8张,在模拟器或者其他设备上为64张。散列表里面有锁,引用计数表和弱引用表。其中锁用来保证线程安全,引用计数表是用来存超过最大个数的引用计数的,弱引用表则是用来储存__weak修饰的变量和weak修饰的属性等。如果在整个系统中只有一张散列表,那么在使用过程中开表和关表,为了保证线程安全就要开锁和解锁,如果所有对象挤在一个表里,就会导致性能等消耗。多张表还可以及时回收相关内存。
2. 弱引用表
这里的打印结果是什么呢?运行后发现是1 和 2,按道理来说,这里的引用计数应该是 1 和 1, 那么为什么weakObjc的引用计数是2呢?
在alloc的时候_class_createInstanceFromZone调用objc->initIsa会进行引用计数赋值为1的操作。而weak修饰的就不会进行引用计数+1的操作,所以objc引用计数为1。
在__weak处打下断点,连续step into 后发现进入了objc_initWeak。
搜索objc_initWeak。
接下来看storeWeak。这里如果是第一次进来的话,就会走到haveNew里面。
往下走会调用weak_register_no_lock进行注册。
来到weak_register_no_lock,看到这里先是做了状态的判断,然后判断弱引用表是否存在,存在则append进去,否则就新建一个插入。
如果有关于这个对象的弱引用表,则进行append_referrer往这个表里添加,并且判断是否进行四分之三扩容。
没有的话就进行对象和指针地址的绑定,然后做相应的扩容以及插入操作。
所以这里的过程是
- 得到散列表的弱引用表
- 创建一个
weak_entry_T - 把
referent加入到weak_table的数组inline_referrers weak_table扩容一下- 把
new_entry加入到weak_table中
在打印的weakObjc引用计数的地方打下断点后运行,发现调用objc_loadWeakRetained。
接下来到objc_loadWeakRetained里面。这里obj是临时变量,而*location则是外面的object对象,这里将object对象赋值给obj,这里看到运行到这里的时候retainCount还是1。
接下来走到rootTryRetain。
这里调用了rootRetain,这里会进行retain操作,就会对引用计数进行+1处理。
出来后看到,这里的引用计数变成了2。所以这里的weakObj的引用计数为2。
objc_loadWeakRetained里面的obj 和 result都是临时变量,所以出了作用域之后就会被释放掉,就会进行release,所以打印两次retainCount不会变为3。
3. 面试题
这样使用timer是否有问题呢?这里会有 self-> self.timer -> self 的循环引用问题。这里timer是无法进行弱引用的否则会奔溃,那么该怎么处理呢?这里用weakself也是没有效果的,因为runloop会强持有target。
这里可以用另外一个带有block的api,这个api不会对self进行强持有。
这里还可以在viewWillDisappear的时候对timer进行处理,这里有个问题就是无论pop还是push都会对timer进行处理。
调用这个api的话,pop的话就会销毁timer,而push的话就不会有影响。
这里还可以用中介者模式来避免循环引用,这里当发现外部objc释放之后,就会在fireHomeWapper自动释放timer。
外部调用:
也可以用虚基类来进行快速或者慢速消息转发。这样就不对self强持有,而是对self.proxy进行强持有。proxy强持有释放与否由self.timer控制的,self.timer释放则是由vc能控制的,只要vc能释放,那么timer和proxy就能正常释放。
外部调用: