OC底层原理探索文档汇总
本文主要是在源码层面上分析内存管理的引用计数的变化过程以及。包括alloc/retain/release/dealloc/retainCount的源码分析
主要内容:
1、引用计数的过程
2、弱引用表的结构
3、sideTable散列表的结构
retain的分析
retain
在源码中全局搜索retain()
源码:
// Equivalent to calling [this retain], with shortcuts if there is no override
//相当于调用[this retain],如果没有重写,则使用快捷方式
inline id
objc_object::retain()
{
ASSERT(!isTaggedPointer());
//如果没有重写retain方法,就会执行默认的
if (fastpath(!ISA()->hasCustomRR())) {
return rootRetain();
}
//如果本类已经重写了,就和普通的方法调用一样直接调用重写的方法即可
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
}
说明:
- 如果我们重写了retain方法,会进行消息发送来调用
- 如果没有重写,就直接调用已有的rootRetain函数
- 因此我们平时要重写retain方法就需要[super retain]
rootRetain
通过retain -> rootRetain() -> rootRetain(bool tryRetain, bool handleOverflow),找到rootRetain函数进行分析
源码:
/*
进行一次reatain真正进行的操作
过程:
1、如果不是nonpointr_isa,就直接操作散列表
2、如果正在释放,就直接dealloc
3、引用计数+1
4、判断如果carry已经满了,就将引用计数的一半放到散列表中,一半放到extra_rc中
*/
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;//小对象类型直接返回,不进行任何操作
bool sideTableLocked = false;
bool transcribeToSideTable = false;//转移到sideTable
//这里使用isa是因为isa中存储有引用计数
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//判断是否为nonpointer isa
if (slowpath(!newisa.nonpointer)) {
//如果不是 nonpointer isa,直接操作散列表sidetable
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
//是否正在释放,直接dealloc
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
uintptr_t carry;
//执行引用计数+1操作,即对bits中的extra_rc进行+1操作,通过1ULL<<45(arm64,用于该对象存储引用计数值
//状态标识carry,用于表示extra_rc是否满了
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
//extra_rc是否加满了
if (slowpath(carry)) {//操作散列表
// newisa.extra_rc++ overflowed
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.
//这里只将一半存入到extra_rc中,剩下的一半存储到散列表中
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
//给isa的extra_rc设置数值
newisa.extra_rc = RC_HALF;
//给isa的has_sidetable_rc设置标识符为YES,表示在散列表中也存储有数据
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
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;
}
代码结构:
- 判断如果是小对象类型,不进行retain,直接返回
- 判断如果不是nonpointer,就直接操作散列表
- 如果是元类就直接返回,元类无需进行引用计数的计算(前面我们分析过,元类不是nonpointer_isa)
- 如果不是元类,直接操作散列表sidetable
- 判断如果该对象正在dealloc,不作任何操作直接退出
- 对isa中的extra_rc进行+1操作,并且通过carry存储是否已满
- 如果extra_rc已经达到最大值,数据已满,carry就是YES
- 当carry为YES,就将extra_rc中的一半取出来赋值到isa中的extra_rc,另一半拿出来存储到sidetable的计数表中。并且设置isa中的has_sidetable_rc为YES,表示散列表中存储有引用计数。
说明:
- 不是nonpointer_isa就只有散列表存储引用计数,这也很容易理解,因为非nonpointer的isa只存储有类信息,没有其他信息。无法存储引用计数
- nonpointer_isa有两个值,一个是extra_rc,一个是has_sidetable_rc,分别用来存储引用计数和是否存储有sidetable。
- 如果extra_rc没有满,引用计数只存储在extar_rc(arm占有19位)
- 如果extra_rc已经满了,就需要取出数据的一半存储在sidetable。并且将isa中的has_sidetable_rc设置为YES(has_sidetable_rc占有两位)
retain的过程:
- 先给isa中的extra_rc引用计数+1
- 如果extra_rc溢出,就取出一半放到sideTable中的引用计数表中。只留一半在extra_rc中
- 如果正在dealloc,就直接dealloc不进行任何操作
sidetable的认识:
源码:
// RefcountMap disguises its pointers because we
// don't want the table to act as a root for `leaks`.
//这个引用计数表是对象作为键,引用计数作为值来存储的
//当引用计数为0时,会自动将其objc删除掉
typedef objc::DenseMap,size_t,RefcountMapValuePurgeable> RefcountMap;
// Template parameters.
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };
//额外的散列表
struct SideTable {
spinlock_t slock;//自旋锁,对于表的使用需要加解锁
RefcountMap refcnts;//引用计数表,仅在未开启isa优化 或 在isa优化情况下isa_t的引用计数溢出时才会用到
weak_table_t weak_table;//弱引用表,弱引用计数存储在这里
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
//可以看到SideTable是不可以析构的,
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
//锁的操作,SideTable的读取需要进行加解锁
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template
static void lockTwo(SideTable *lock1, SideTable *lock2);
template
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
说明:
- 额外信息表SideTable存储有引用计数表和弱引用表
- 引用计数表仅在未开启isa优化 或 在isa优化情况下isa_t的引用计数溢出时才会用到
- 这个引用计数表是对象作为键,引用计数作为值来存储的
- 当一个对象被弱引用时,就需要在弱引用表中进行存储
- 同时在这里还有一个自旋锁,是通过spinlock_t来实现的
- 自旋锁简单来说会忙等待,效率高。
- 加锁是为了线程安全,防止对引用计数的读取出现差错。
- 同时看到SideTable是不可以删除的,这个是系统自己创建的,我们无法删除
weak_table_t
源码:
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
* 全局弱引用表。
* 将对象ID存储为键,将weak_entry_t结构体存储为值
*/
struct weak_table_t {
weak_entry_t *weak_entries;//是一个动态数组,里面存储了很多的弱引用
size_t num_entries;//弱引用的数量
uintptr_t mask;//用来哈希计算,为总数-1
uintptr_t max_hash_displacement;//可能发生的最大冲突数
};
说明:
- 可以看到weak_table_t这是一个全局的弱引用表,里面存储有多个弱引用表
- 在weak_entries中对象ID作为键,weak_entry_t作为值存储。
总结:
- 弱引用表存在于sideTable表中
- isa中有weakly_refrenced来判断是否存在弱引用
- 弱引用表使用对象ID作为键,弱引用动态数组作为值存储
release
通过objc_release() -> release() -> rootRelease() -> rootRelease(bool performDealloc, bool handleUnderflow)查找到是在rootRelease函数中进行了release操作
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
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)) {//如果不是nonpointer_isa,就直接操作sideTable,进行减一
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
//进行引用计数-1操作,即extra_rc-1
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);
//如果此时extra_rc的值为0了,则走到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;
underflow:
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
// 如果isa中的extra_rc减完之后已经为0,就开始从sideTable获取数据,或者如果全部为0,就开始deallocate
// abandon newisa to undo the decrement
newisa = oldisa;
//通过has_sidetable_rc判断散列表中是否存在计数
if (slowpath(newisa.has_sidetable_rc)) {
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
// Transfer retain count from side table to inline storage.
//从sideTable转移引用计数到extra_rc
//先上锁
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.
//即使已经为0了,也需要设置has_sidetable_rc
if (borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
//extra_rc有数据后,就再-1
newisa.extra_rc = borrowed - 1; // redo the original decrement too,计数-1
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.
//如果没有就直接dealloc
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;
}
说明:
- 过程与retain的刚好相反
- 先直接将isa中的extra_rc中的数据进行-1
- 如果已经为0,则从sideTable获取数据
- 如果sideTable中也没有数据,就进行dealloc
- 如果有数据,就取出一半的数据放到extra_rc中,再对extra_rc进行-1
release的过程为:
- 如果isa中的extra_rc引用计数值不为0,就直接减1;
- 如果值为0,就先从sideTable的引用计数获取值,取出其中的一半存储到extra_rc中,并继续-1.
- 如果减完之后引用计数为0,就开始dealloc。
dealloc
在源码中通过dealloc -> _objc_rootDealloc -> rootDealloc,在rootDealloc中进行真正的dealloc操作
rootDealloc
源码:
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
//如果是nonpointer、而且有弱引用,关联对象、是否有析构器,是否有存储的引用计数
/*
如果是nonpointer_isa,不会进
如果不是nonpointer_isa,这几个条件有一个还在,就不会进
*/
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
//如果非nonpointer_isa或者是nonpointer_isa,且存在那几个条件就需要销毁一些东西
else {
object_dispose((id)this);
}
}
说明:
- 如果是小对象类型无需操作
- 如果是nonpointer_isa,但是不存在弱引用表、关联对象、析构器、额外数据表sidetable,就可以直接删掉
- 如果是非nonpointer_isa,或者是nonpointer_isa,但是存在弱引用表、关联对象、析构器、额外数据表sidetable,就需要先删除这些东西。
object_dispose
源码:
id
object_dispose(id obj)
{
if (!obj) return nil;
//先破坏对象
objc_destructInstance(obj);
//再清除空间
free(obj);
return nil;
}
说明:
- 这里只是调用objc_destructInstance进行销毁对象,之后再清除对象的空间
objc_destructInstance
源码:
/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory. 这里只销毁实例,不在这里清除内存
* Calls C++ destructors.调用C++析构器
* Calls ARC ivar cleanup.调用ARC变量清除
* Removes associative references.移除关联对象
* Returns `obj`. Does nothing if `obj` is nil.将返回obj,如果obj是nil,将不进行任何操作
**********************************************************************/
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
//这个顺序很重要。
/*
1、先进行C++析构函数调用
2、移除关联对象
3、弱引用
4、sideTable
*/
/*
如果是nonapoint_isa,,不移除弱引用
*/
if (cxx) object_cxxDestruct(obj);//C++析构函数
if (assoc) _object_remove_assocations(obj);//删除关联对象
obj->clearDeallocating();
}
return obj;
}
clearDeallocating
源码:
/*
删除has_sidetable_rc和extra_rc
*/
inline void
objc_object::clearDeallocating()
{
//如果不是nonpointer,清除has_sidetable_rc和extra_rc
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());
}
说明:
- 这里只销毁,不清除内存
- 一个是注意销毁的内容包括C++析构函数、关联对象、弱引用、sideTable。
- 第二个是注意销毁的顺序为C++析构函数、关联对象、弱引用、sideTable。
总结:
- dealloc时需要先清除C++析构函数、关联对象、弱引用、sideTable,通过isa来判断是否需要清除
- 清除的顺序为C++析构函数、关联对象、弱引用、sideTable
retainCount
源码:
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;//小对象类型不用处理
sidetable_lock();
isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
//如果是nonpointer,取出extra_rc和sidetable中的引用计数加在一起取出来
if (bits.nonpointer) {
uintptr_t rc = bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
说明:
- extra_rc和sidetable中的引用计数加在一起就是这个对象的引用计数值
看一个面试题打印什么?
- (void)test2{
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)[NSObject alloc]));
}
说明:
- 查看运行结果打印的是1。
- 此处我们并没有将这个对象附给任何变量,但是就已经引用计数+1了。
初始化isa源码
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
说明:
- 其他的以前均已经分析过,这里单看newisa.extra_rc = 1;这一条语句,在初始化isa的时候就已经给extra_rc设置为1。
- 这也就是当alloc一个对象之后引用计数就为1.
总结:
- retainCount得到的值是isa中的extra_rc中的值加上sideTable中的引用计数的值
- 当alloc创建出对象时,默认引用计数为1