一、内存分布
当程序运行时,系统会开辟内核区、用户区。
值类型:存入栈;无需进行内存管理 引用类型:存入堆;无需进行内存管理
二、iOS的内存管理方案
2.1 Tagged Pointer(无需进行引用计数)
-
Tagged Pointer 是一个指针,指针中包含
Tagged
标记,用于区分存储的数据类型,同时将值也存储在指针中,通过位运算将其编码成一个指针格式
- 针对小对象(存储内容小):NSDate、NSNumber、NSString、NSIndexPath 等
- 使用后不再存储地址,而是真正的值;3倍的空间效率,106倍的访问速度
- 在0的位置打上标记(Intel最低位为1,ARM最高位为1)
- 初始化时会与一个随机值进行混淆
2.2 举例说明Tagged Pointer
以 NSString 为例,读取一个常规的 NSString,通过 栈区存储的指针地址,找到堆区空间,再从堆区读取到字符串的值,整个读取流程效率较低,所以,系统对其进行优化,如果 NSString 存储的字符串长度较短(<= 9
),会使用 Tagged Pointer 存储。
三、引用计数
在之前我们探索alloc原理的时候就探索过isa的方法initIsa,探索流程如下:objc_alloc–>callAlloc–>_objc_rootAllocWithZone–>_class_createInstanceFromZone–>initIsa,这里有一extra_rc来存储引用计数:
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) { // 非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 // 创建isa,引用计数赋值为1 newisa.extra_rc = 1; } isa = newisa; }
存储引用计数有下面这几种形式:
1.非nonpointerIsa的直接存在sidetable中 2.是nonpointerIsa的如果在extra_ac中能存下,就存在这里 3.如果extra_ac中存不下,就将一半放到sidetable中,并将has_sidetable_rc标志位置为true
3.1 nonpointerIsa(开启指针优化的指针)
1.现在大部分指针都是nonpointerIsa 2.非纯指针类型的 isa,isa 中包含了 类信息、对象的引⽤计数 等
3.2 retain和release的原理
3.2.1 retain
(1)在objc的源码中找objc_retain
__attribute__((aligned(16), flatten, noinline)) objc_retain(id obj) { // 如果是 tagged pointer 就直接返回对象,也就是说无需引用计数管理 if (obj->isTaggedPointerOrNil()) return obj; return obj->retain(); }
(2) 查看objc_retain中的rootRetain的源码
ALWAYS_INLINE id objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant) { if (slowpath(isTaggedPointer())) return (id)this; bool sideTableLocked = false; bool transcribeToSideTable = false; isa_t oldisa; isa_t newisa; oldisa = LoadExclusive(&isa.bits); if (variant == RRVariant::FastOrMsgSend) { // These checks are only meaningful for objc_retain() // They are here so that we avoid a re-load of the isa. if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) { ClearExclusive(&isa.bits); if (oldisa.getDecodedClass(false)->canCallSwiftRR()) { return swiftRetain.load(memory_order_relaxed)((id)this); } return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain)); } } if (slowpath(!oldisa.nonpointer)) { // a Class is a Class forever, so we can perform this check once // outside of the CAS loop if (oldisa.getDecodedClass(false)->isMetaClass()) { ClearExclusive(&isa.bits); return (id)this; } } do { transcribeToSideTable = false; newisa = oldisa; if (slowpath(!newisa.nonpointer)) { ClearExclusive(&isa.bits); if (tryRetain) return sidetable_tryRetain() ? (id)this : nil; else return sidetable_retain(sideTableLocked); } // don't check newisa.fast_rr; we already called any RR overrides // 对象正在被释放则直接返回 if (slowpath(newisa.isDeallocating())) { ClearExclusive(&isa.bits); if (sideTableLocked) { ASSERT(variant == RRVariant::Full); sidetable_unlock(); } if (slowpath(tryRetain)) { return nil; } else { return (id)this; } } uintptr_t carry; // 是nonpointerIsa,且 extra_rc 能容纳 newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ if (slowpath(carry)) { // 是nonpointerIsa,但 extra_rc 容纳不下 // newisa.extra_rc++ overflowed if (variant != RRVariant::Full) { 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; // extra_rc改为只存一半引用计数 newisa.extra_rc = RC_HALF; // 一半存到 sidetable 中,要打上标记 newisa.has_sidetable_rc = true; } } while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits))); if (variant == RRVariant::Full) { if (slowpath(transcribeToSideTable)) { // Copy the other half of the retain counts to the side table. // sidetable中存入剩余一半引用计数 sidetable_addExtraRC_nolock(RC_HALF); } if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock(); } else { ASSERT(!transcribeToSideTable); ASSERT(!sideTableLocked); } return (id)this; }
3.2.1 release
(1)在objc的源码中找objc_release
__attribute__((aligned(16), flatten, noinline)) void objc_release(id obj) { // 如果是 tagged pointer 就不做任何操作,无需引用计数管理 if (obj->isTaggedPointerOrNil()) return; return obj->release(); }
(2)release流程与retain相反,无需赘述,需要注意的是当引用计数减为0时,会向对象发送dealloc消息
ALWAYS_INLINE bool objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant) { ...... deallocate: // Really deallocate. ASSERT(newisa.isDeallocating()); ASSERT(isa.isDeallocating()); if (slowpath(sideTableLocked)) sidetable_unlock(); __c11_atomic_thread_fence(__ATOMIC_ACQUIRE); if (performDealloc) { // 发送dealloc消息 ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc)); } return true; }
3.3 dealloc
(1)在oc的源码搜索_objc_rootDealloc
inline void objc_object::rootDealloc() { if (isTaggedPointer()) return; // fixme necessary? // 是nonpointer 并且同时满足以下条件时才会free if (fastpath(isa.nonpointer && // 没有弱引用 !isa.weakly_referenced && // 没有关联对象 !isa.has_assoc && #if ISA_HAS_CXX_DTOR_BIT // 没有C++的析构函数 !isa.has_cxx_dtor && #else !isa.getClass(false)->hasCxxDtor() && #endif // 没有 sidetable 借位 !isa.has_sidetable_rc)) { assert(!sidetable_present()); // 释放对象 free(this); } else { // 调用C++析构函数、删除关联对象、清空散列表中引用计数信息、清空弱引用表中相关数据 object_dispose((id)this); } }
(2)当对象为nonpointerIsa,并且没有弱引用、没有关联对象,没有C++析构函数、没有sidetable错位时才对对象进行释放,否则先将这些内容抹掉再free对象。
原创文章,作者:wure,如若转载,请注明出处:https://blog.ytso.com/270777.html