- 小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
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
就能正常释放。
外部调用: