oc和swift都是使用引用计数进行管理对象。 每个对象的引用计数是如何存储的?
isa是一个64位的联合体位域,根据CPU架构的不同每个成员的长度不同。
isa结构
isa指针中的extra_rc
用于存放引用计数,当引用计数达到一定值时会存储到散列表中。
在objc4-781源码中,查找源码
objc_setProperty
->reallySetProperty
->objc_retain
->retain
->rootRetain
其中reallySetProperty主要是对新值的retain,旧值的release。
retain
retain主要工作:
-
- 判断是否为taggedPointer类型,是返回自身,
-
- 如果不是taggedPointer类型,操作引用计数
- 2.1 判断是否为nonpointer类型,不是则直接操作SideTables散列表,返回散列表中的内容
- 2.2 判断是否正在释放,如果正在释放,则执行dealloc流程
- 2.3 执行引用计数
extra_rc+1
操作,并返回引用计数isa->extra_rc
的状态标识carry,用于判断extra_rc是否满了 - 2.4 如果extra_rc中的引用计数满了,此时需要操作散列表,将满状态的引用计数一半存到extra_rc,另一半存在散列表的rc_half中。
如果都存储在散列表,每次对散列表操作都需要开解锁,操作耗时,消耗性能大,此操作的目的在于提高性能
nonpinter
: 表示是否对 isa 指针开启指针优化,
- 0:纯isa指针,
- 1:经过优化后的isa指针
理解
slowpath
和fastpath
,用于在编译器编译时判断是否需要进行指令优化,如果是fastpath会让cpu提前预读该语句下分支为1的一条指令,从而减少指令跳转带来的开销。
简单理解fastpath执行该代码的概率大,从而让cpu提前做好准备。slowpath执行该代码的概率小。
源码如下
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
//1.判断是否为taggedpointer类型
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//2.1 不是nonpointer类型,直接操作散列表
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return (id)this;
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
// don't check newisa.fast_rr; we already called any RR overrides
//2.2 判断是否正在释放
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
//2.3 extra_rc+1操作,carry表示是否慢
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
//2.4 如果满了,
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
// 如果不处理溢出情况,则在这里会递归调用一次,再进来的时候,handleOverflow会被rootRetain_overflow设置为true,从而进入到下面的溢出处理流程
if (!handleOverflow) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
}
// 将oldisa 替换为 newisa,并赋值给isa.bits(更新isa_t), 如果不成功,do while再试一遍
while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
//将extra_rc中一半存入散列表
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
具体流程图如下:
散列表
oc将isa中满了的引用计数存储在散列表中,散列表有多张,ios有8张,macos有64张,
操作散列表会进行锁操作会有开销,如果所有的数据都在一张表不安全,而每个对象都有一个散列表开销更大,控制在8张。每个散列表中有refcnts
引用计数表(哈希表)存储每个对象的引用计数。
散列表的结构如下
struct SideTable {
spinlock_t slock; //自旋锁
RefcountMap refcnts; //引用计数表
weak_table_t weak_table; //弱引用表
...
};
使用散列表的原因
- 数组:特点数据的是读取快,存储不方便
- 链表:增删方便,查询慢(需要从头节点开始遍历查询)
- 散列表:本质就是一张哈希表,哈希表集合了数组和链表的长处,增删改查都比较方便
release
根据retain原理,release进行减一操作。
-
- 判断是否为TaggedPointer类型,是就返回false
-
- 如果不是TaggedPointer,操作引用计数
- 2.1 判断是否nonpointer,如果是纯指针(即值为0),对散列表中的引用计数减一
- 2.2 如果nonpointer是1,
extra_rc--
,并返回carry状态 - 2.3 根据carry判断,extra_rc是否还有引用计数,carry为1表示extra_rc无值,跳转到underflow中处理散列表。
- 2.4 extra_rc还有引用计数,则返回false
- 3 underflow中处理散列表
- 3.1 如果has_sidetable_rc为true,取出散列表中还有一半的引用计数-1,并存储到extra_rc
- 3.2 如果performDealloc 为true,则执行delloc
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
//判断是否为taggedPointer类型
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//纯指针类型,操作散列表
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return false;
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
// don't check newisa.fast_rr; we already called any RR overrides
//引用extra_rc--操作
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
//如果extra_rc不存在值了,跳转underflow中
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
//处理extra_rc中没有引用计数的情况
underflow:
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
// abandon newisa to undo the decrement
newisa = oldisa;
//判断是否关联散列表
if (slowpath(newisa.has_sidetable_rc)) {
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
// Transfer retain count from side table to inline storage.
//散列表开锁
if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
goto retry;
}
// Try to remove some retain counts from the side table.
//取出散列表的引用计数
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
// To avoid races, has_sidetable_rc must remain set
// even if the side table count is now zero.
if (borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
newisa.extra_rc = borrowed - 1; // redo the original decrement too
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if (!stored) {
// Inline update failed.
// Try it again right now. This prevents livelock on LL/SC
// architectures where the side table access itself may have
// dropped the reservation.
isa_t oldisa2 = LoadExclusive(&isa.bits);
isa_t newisa2 = oldisa2;
if (newisa2.nonpointer) {
uintptr_t overflow;
newisa2.bits =
addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits,
newisa2.bits);
}
}
}
if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
// Decrement successful after borrowing from side table.
// This decrement cannot be the deallocating decrement - the side
// table lock and has_sidetable_rc bit ensure that if everyone
// else tried to -release while we worked, the last one would block.
sidetable_unlock();
return false;
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
// Really deallocate.
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
// does not actually return
}
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
dealloc
释放对象需要判断isa指针各成员的值
nonpointer
: 是否为优化过的指针,为1
weakly_referenced
: 弱引用表,为0
has_assoc
:是否存在关联对象,为0
has_cxx_dtor
:是否有C++析构函数,为0
has_sidetable_rc
: 是否关联散列表,为0
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
由于这里使用了fastPath,dealloc过程绝大多数情况都是在上述条件成立的情况下自动释放对象。
查看object_dispose源码
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
//判断是否有c++析构函数
bool cxx = obj->hasCxxDtor();
//判断是否有关联对象
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
//判断弱引用表,散列表是否有值
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
CFGetRetainCount
获取对象的引用计数的数量
NSObject *obj =[NSObject alloc];
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
输出
alloc主要做了三件事
- 1.计算对象的内存占用空间
-
- 分配内存空间
-
- 初始化isa指针,
alloc并没有对retain加一操作,查看objc4-781
中retainCount
源码
retainCount
->_objc_rootRetainCount
->rootRetainCount
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) {
//此时读取extra_rc 加一后判断散列表中是否有值,然后返回rc,此过程并没有读写操作
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
此时读取extra_rc
加一后判断散列表中是否有值,然后进行操作后返回rc,此过程并没有写操作
而在最新的objc818.2
源码中取消掉了加一操作,可以猜测ios14系统还没使用objc818的源码。
总结:
-
- retain:retain并不是简单的对
isa->extra_rc
加一,如果extra_rc
满了会存储一半的值到散列表。
- retain:retain并不是简单的对
-
- release:
isa->extra_rc
减一,如果extra_rc值为空再去判断散列表中查找,散列表有值将其存入extra_rc
,没有等待释放
- release:
-
- dealloc:释放时判断弱引用表,关联对象,散列表,C++析构
-
- retaincount:读取
isa->extra_rc
后加一,在去查找散列表中是否有值
- retaincount:读取