一、内存分布
当程序运行时,系统会开辟内核区、用户区。
值类型:存入栈;无需进行内存管理 引用类型:存入堆;无需进行内存管理
二、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/tech/aiops/270777.html