- 小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
1. NONPOINTER_ISA
之前的文章中介绍过 NONPOINTER_ISA,知道对象的引用计数是存在extra_rc里面的。如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到下面的 has_sidetable_rc。
1.1 retain 流程分析
- 判断是否为Tagged Pointer,如果是则不进行操作直接返回,否则就往下走
- 判断是否为 NONPOINTER_ISA,如果不是则直接通过散列表(sidetable)进行retain操作,这里进行+2是因为散列表将引用计数存在63号位置上,+2相当于63号位上的+1操作。是的话就往下走。
- 判断是否正在析构,如果是则进行相关操作后返回
- 执行extra_rc+1,即引用计数+1操作,并给一个引用计数的状态标识carry,用于表示extra_rc是否满了(最大值为256)
- 如果满了,那么就将一半的引用计数存在散列表,一半存在extra_rc。这里分一半是因为通过extra_rc操作引用计数更加的方便,而散列表则需要先去寻找表,再去找到引用计数存储的地方 进行操作,并且散列表还有开锁解锁的操作。
1.2 release 流程分析
- 判断是否为Tagged Pointer,如果是则不进行操作直接返回,否则就往下走
- 判断是否为 NONPOINTER_ISA,如果不是则直接通过散列表(sidetable)进行release操作。
- 判断是否正在析构,如果是则进行相关操作后返回false
- 对extra_rc中的引用计数值进行-1操作,并存储此时的extra_rc状态到carry中
- 如果此时的状态carray为0,则走到underflow流程
- 判断是否有散列表,如果没有则进行deallocate,发送dealloc消息给对象。如果有则从散列表取一半出来进行--操作后赋值给extra_rc,散列表里面的引用计数也变为原来的一半。
2. 面试题
下面代码是否会崩溃呢?这里上面的代码不会崩溃,下面的代码会崩溃。崩溃的原因是一直进行多线程的读和写,写的过程中对新值的retain和对旧值的release。所以会产生一个瞬间旧值已经释放了但是还是继续release,所以崩溃了。那么为什么Tagged Pointer不会有这个问题呢?
看到rootRetain,发现这里如果是TaggedPointer就不做处理直接return。而rootRelease也是一样的。