iOS内存管理 —— 散列表和弱引用表

744 阅读3分钟

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能释放,那么timerproxy就能正常释放。

在这里插入图片描述

在这里插入图片描述

外部调用:

在这里插入图片描述