前言
我们知道内存管理在任何一门编程语言中都有极其重要的地位,即然极其重要,也就意味着有难点,今天我们就来剖析iOS的内存管理相关的知识。
1 内存五大区
内核区,用户区。
用户内存五大区:堆、栈、bss(未初始化数据)、data(已初始化数据)、text(代码段)。
- 栈区:局部变量,方法参数,函数,内存地址一般为:0x7开头
- 堆区:通过alloc分配的对象,block copy,内存地址一般为:0x6开头
- BSS段:未初始化的全局变量,静态变量,内存地址一般为:0x1开头
- 数据段: 初始化的全局变量,静态变量,内存地址一般为:0x1开头
- text:程序代码,加载到内存中
栈区的内存是通过sp寄存器来定位。
栈区的速度要比堆区速度快
2 内存管理方案
内存管理方案一般有:ARC/MRC
在ARC/MRC比较常见有taggedPointer、NONPOINTER_ISA、散列表(引用计数表,弱引用表)
我们就来分析下这些内存管理方案。
2.1 taggedPointer底层分析
TaggedPointer:小对象-NSNumber,NSDate
TaggedPointer 是一个指针,会比普通的指针多了Tagged,针对小对象,NSNumber,NSDate,NSString,UIColor,我们存这些对象的时候,其实用不到64位,为了节省内存空间,使用其部分位,小对象=指针 + 存储的内容。
2.1.1 x86-64下的taggedPointer结构
我们看以下代码,来分析taggedPointer在x86上的结构
代码如下
uintptr_t
ro_objc_decodeTaggedPointer(id ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
- (void)indexPathDemo{
NSString *str = [NSString stringWithFormat:@"ro"];
NSLog(@"%p-%@-%@",str,str,str.class);
NSLog(@"%p-%@ -%@- 0x%lx",str,str,str.class,ro_objc_decodeTaggedPointer(str));
}
运行项目,如图
我们知道堆区是0x7开头,栈是0x6开头,全局0x1开头,那么这个地址很奇怪,我们来分析下。
我们在objc源码中搜下taggedPointer,如下所示
// payload = (decoded_obj << payload_lshift) >> payload_rshift
payload就是有效负载,经过位运算,加密和解密的过程。
我们分析一下decoded_obj这个,源码中,搜索decoded,找到如下代码
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
uintptr_t value = _objc_decodeTaggedPointer_noPermute(ptr);
#if OBJC_SPLIT_TAGGED_POINTERS
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
value |= _objc_obfuscatedTagToBasicTag(basicTag) << _OBJC_TAG_INDEX_SHIFT;
#endif
return value;
}
_objc_decodeTaggedPointer_noPermute的源码如下
static inline uintptr_t
_objc_decodeTaggedPointer_noPermute(const void * _Nullable ptr)
{
uintptr_t value = (uintptr_t)ptr;
#if OBJC_SPLIT_TAGGED_POINTERS
if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
return value;
#endif
return value ^ objc_debug_taggedpointer_obfuscator;
}
value ^ objc_debug_taggedpointer_obfuscator;这里进行异或运行,混淆。
我们再找下objc_debug_taggedpointer_obfuscator这个,
/***********************************************************************
* initializeTaggedPointerObfuscator
* Initialize objc_debug_taggedpointer_obfuscator with randomness.
*
* The tagged pointer obfuscator is intended to make it more difficult
* for an attacker to construct a particular object as a tagged pointer,
* in the presence of a buffer overflow or other write control over some
* memory. The obfuscator is XORed with the tagged pointers when setting
* or retrieving payload values. They are filled with randomness on first
* use.
**********************************************************************/
static void
initializeTaggedPointerObfuscator(void)
{
if (!DisableTaggedPointerObfuscation) {
// Pull random data into the variable, then shift away all non-payload bits.
arc4random_buf(&objc_debug_taggedpointer_obfuscator,
sizeof(objc_debug_taggedpointer_obfuscator));
objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
#if OBJC_SPLIT_TAGGED_POINTERS
// The obfuscator doesn't apply to any of the extended tag mask or the no-obfuscation bit.
objc_debug_taggedpointer_obfuscator &= ~(_OBJC_TAG_EXT_MASK | _OBJC_TAG_NO_OBFUSCATION_MASK);
// Shuffle the first seven entries of the tag permutator.
int max = 7;
for (int i = max - 1; i >= 0; i--) {
int target = arc4random_uniform(i + 1);
swap(objc_debug_tag60_permutations[i],
objc_debug_tag60_permutations[target]);
}
#endif
} else {
// Set the obfuscator to zero for apps linked against older SDKs,
// in case they're relying on the tagged pointer representation.
objc_debug_taggedpointer_obfuscator = 0;
}
}
initializeTaggedPointerObfuscator这里初始化
arc4random_buf(&objc_debug_taggedpointer_obfuscator,
sizeof(objc_debug_taggedpointer_obfuscator));
objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
生成一个随机数,在整个内存里是一个encode状态,它的真正地址是需要decode。
通过两个异或(相同为0,不同为1)运算,就可以得到真实的值。
uintptr_t
ro_objc_decodeTaggedPointer(id ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
所以这里我们通过这样的异或运算就可以得到真实的值。
运行项目,如图
0xa00000000006f722就是真实的指针,我们解析下这个地址。
-
第1位的1代表是taggedPointer类型
我们再来看下面一张图,如
114对应o,111对应r,也就是我们的字符串。
我们再改下代码,如
NSString *str = [NSString stringWithFormat:@"ro"];
NSLog(@"%p-%@-%@",str,str,str.class);
NSLog(@"%p-%@ -%@- 0x%lx",str,str,str.class,ro_objc_decodeTaggedPointer(str));
NSString *str1 = [NSString stringWithFormat:@"r"];
NSLog(@"%p-%@ -%@- 0x%lx",str1,str1,str1.class,ro_objc_decodeTaggedPointer(str1));
NSString *str2 = [NSString stringWithFormat:@"robert"];
NSLog(@"%p-%@ -%@- 0x%lx",str2,str2,str2.class,ro_objc_decodeTaggedPointer(str2));
//
NSNumber *number = @6;
NSLog(@"%p-%@ -%@- 0x%lx",number,number,number.class,ro_objc_decodeTaggedPointer(number));
我们看下结果,如图
0xa对应的是NSString,0xb对应的是NSNumber,我们来看下objc_tag
{
// 60-bit payloads
OBJC_TAG_NSAtom = 0,
OBJC_TAG_1 = 1,
OBJC_TAG_NSString = 2,
OBJC_TAG_NSNumber = 3,
OBJC_TAG_NSIndexPath = 4,
OBJC_TAG_NSManagedObjectID = 5,
OBJC_TAG_NSDate = 6,
// 60-bit reserved
OBJC_TAG_RESERVED_7 = 7,
// 52-bit payloads
OBJC_TAG_Photos_1 = 8,
OBJC_TAG_Photos_2 = 9,
OBJC_TAG_Photos_3 = 10,
OBJC_TAG_Photos_4 = 11,
OBJC_TAG_XPC_1 = 12,
OBJC_TAG_XPC_2 = 13,
OBJC_TAG_XPC_3 = 14,
OBJC_TAG_XPC_4 = 15,
OBJC_TAG_NSColor = 16,
OBJC_TAG_UIColor = 17,
OBJC_TAG_CGColor = 18,
OBJC_TAG_NSIndexSet = 19,
OBJC_TAG_NSMethodSignature = 20,
OBJC_TAG_UTTypeRecord = 21,
// When using the split tagged pointer representation
// (OBJC_SPLIT_TAGGED_POINTERS), this is the first tag where
// the tag and payload are unobfuscated. All tags from here to
// OBJC_TAG_Last52BitPayload are unobfuscated. The shared cache
// builder is able to construct these as long as the low bit is
// not set (i.e. even-numbered tags).
OBJC_TAG_FirstUnobfuscatedSplitTag = 136, // 128 + 8, first ext tag with high bit set
OBJC_TAG_Constant_CFString = 136,
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
};
这里就不再一一验证了。
2.1.1 arm64下的taggedPointer
我们再来分析下真机下的效果,相对于模拟器,要复杂一些,代码如下
#define kc_OBJC_TAG_INDEX_MASK 0x7UL
#define kc_OBJC_TAG_INDEX_SHIFT 0
extern uint8_t objc_debug_tag60_permutations[8];
uintptr_t ro_objc_obfuscatedTagToBasicTag(uintptr_t tag) {
for (unsigned i = 0; i < 7; i++)
if (objc_debug_tag60_permutations[i] == tag)
return i;
return 7;
}
uintptr_t
ro_objc_decodeTaggedPointer(id ptr)
{
uintptr_t value = (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
uintptr_t basicTag = (value >> kc_OBJC_TAG_INDEX_SHIFT) & kc_OBJC_TAG_INDEX_MASK;
value &= ~(kc_OBJC_TAG_INDEX_MASK << kc_OBJC_TAG_INDEX_SHIFT);
value |= ro_objc_obfuscatedTagToBasicTag(basicTag) << kc_OBJC_TAG_INDEX_SHIFT;
return value;
}
static inline uintptr_t ro_objc_basicTagToObfuscatedTag(uintptr_t tag) {
return objc_debug_tag60_permutations[tag];
}
void *
ro_objc_encodeTaggedPointer(uintptr_t ptr)
{
uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);
uintptr_t basicTag = (value >> kc_OBJC_TAG_INDEX_SHIFT) & kc_OBJC_TAG_INDEX_MASK;
uintptr_t permutedTag = ro_objc_basicTagToObfuscatedTag(basicTag);
value &= ~(kc_OBJC_TAG_INDEX_MASK << kc_OBJC_TAG_INDEX_SHIFT);
value |= permutedTag << kc_OBJC_TAG_INDEX_SHIFT;
return (void *)value;
}
真机跟模拟器有不一样的地方。
- 第1位是标记taggedPointer
- 类型是存储低三位 char 0,short 1,int 2,3 long,4 float,5 double。
- 再住走四位,存储的是数据的长度
- 再往后是数据的内容
由于真机出现问题,暂时无法详细写出验证流程,与模拟器原理相似。
2.1.1 taggedPointer面试题分析
//案例1
- (void)taggedPointerDemo {
self.queue = dispatch_queue_create("com.robert.ccom", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"robert"];
NSLog(@"%@",self.nameStr);
});
}
}
//案例2,连续点击
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
for (int i = 0; i<100000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"3333333fdsdsfsafsafasfasfasfsafafafafda"];
NSLog(@"%@",self.nameStr);
});
}
}
以上代码的运行会不会产生崩溃?
答案是肯定的。
分析如下:
- 案例1 taggedPointer类型的string
- 案例2 是普通的的string
- 案例2中会产生多线程的读写操作
- 案例2中会把setter方法操作,对新值的retain和旧值的release,所以在某一个瞬间会释放多次,产生对野指针的操作
- 案例2中NSCFString原因是因为数据太长,taggedPointer针对小对象的,根本不受ARC的影响
部分源码如下
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
if (slowpath(isTaggedPointer())) return (id)this;
if (slowpath(isTaggedPointer())) return (id)this;在这里如果是小针对直接返回,release同样的道理,部分源码如下
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
if (slowpath(isTaggedPointer())) return false;
- 小对象的值直接存在指针里面,普通栈结构的回收,根本不需在这里处理
- 在objc_msgSend中,如果是小对象根本不会进行慢速查找流程
2.2 NONPOINTER_ISA
taggedPointer对类型的处理
NONPOINTER_ISA:非指针型isa
他们都会对位置进行处理,如下
- nonpointer:表示是否对 isa 指针开启指针优化
0:纯isa指针,1:不⽌是类对象地址,isa 中包含了类信息、对象的引⽤计数等 - has_assoc:关联对象标志位,0没有,1存在
- has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象
- shiftcls:存储类指针的值(相当于taggedPointer的payload)。开启指针优化的情况下,在arm64架构中有33位⽤来存储类指针。
- magic:⽤于调试器判断当前对象是真的对象还是没有初始化的空间
- weakly_referenced:志对象是否被指向或者曾经指向⼀个ARC的弱变量,没有弱引⽤的对象可以更快释放。
- deallocating:标志对象是否正在释放内存
- has_sidetable_rc:当对象引⽤技术⼤于10时,则需要借⽤该变量存储进位
- extra_rc:当表示该对象的引⽤计数值,实际上是引⽤计数值减1,例如,如果对象的引⽤计数为10,那么extra_rc为9。如果引⽤计数⼤于10,则需要使用到has_sidetable_rc。
3 retain及release分析
3.1 MAR&ARC概念
ARC是LLVM和Runtime配合的结果。
ARC中禁⽌⼿动调⽤retain/release/retainCount/dealloc
ARC新加了weak、strong属性关键字
3.2 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;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {
// 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;
newisa.extra_rc = RC_HALF;
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_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
} else {
ASSERT(!transcribeToSideTable);
ASSERT(!sideTableLocked);
}
return (id)this;
}
我们的引用计数存在isa里面,也就是在extra_rc中,在这里引用计数存满时,会放到散列表中。
reatain流程:
- if (slowpath(isTaggedPointer())) return (id)this;判断是否是taggedPointer,是的话直接返回
- isa_t oldisa;为了处理extra_rc,下面的do while循环是要着重分析的,
- if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain(sideTableLocked);
}
这里判断是不是nonpointer,如果是,isa位的操作 - 如果不是nonpointer,调用sidetable_retain这个函数,
id
objc_object::sidetable_retain(bool locked)
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
if (!locked) table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
获取SideTables的数据,然后执行+ 1<<2操作,也就是在原来的值上+2操作, 为什么是这样的,我们需要分析SideTables的结构。
#define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1)),WORD_BITS,如果是64位,左移1位,也就是63位,2的二进制是010,其它位置不变,2号位置+1操作,在2号位置存储引用计数的值
- 如果是nonpointer,执行这段代码
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
if (slowpath(tryRetain)) {
return nil;
} else {
return (id)this;
}
}
*判断是不是在析构,如果在析构就没必要-1操作,在多线程的情况下,已经在释放,还有可能-1。
- 接着在这里
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
在addc中,拿到bits,执行++操作,# define RC_ONE (1ULL<<45),uintptr_t extra_rc : 19,extra_rc是在最后19位,刚好是64位,左移45位刚好是extra_rc,然后执行++1操作,uintptr_t extra_rc : 8定义,说明extra_rc是8位,2的8次方256位,如果carry加多了,这个时候,执行以下代码
if (slowpath(carry)) {
// 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;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
在散列表中存储,这里rootRetain_overflow调用这个函数判断所有超载。
接着
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
执行这段代码,打开锁,newisa.extra_rc = RC_HALF; #define RC_HALF (1ULL<<18),左移18位,也就是移一半,把一半存在存在了extra_rc,另一半标记为has_sidetable_rc,之后调用sidetable_addExtraRC_nolock这个函数,加入到引用计数表中。
所以extra_rc存一半,散列表(引用计数表)存一半。
- extra_rc存在isa中,直接获取到,散列表需要先查表,size_t& refcntStorage = table.refcnts[this];找到相关对象存储的区域,再移一半。
通过散列表(引用计数表)增删改查,同时加锁解锁,是比较复杂,效率低。
那为什么extra_rc还只要存一半?。 - 如果执行release的--操作时,如果全部存储在散列表中,当满了的情况,去散列表(引用计数表)中去获取,还是有问题,比如300,执行-1操作,299,如果放在extra_rc是放不下的(extra_rc最值256),这样操作比较耗费性能。
- 所以各存一半,在extra_rc存一半128位,也足够执行--操作了。
3.3 release的流程分析
我们在objc源码搜索,经过搜索找到如下源码
bool
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
if (slowpath(isTaggedPointer())) return false;
bool sideTableLocked = false;
isa_t newisa, oldisa;
oldisa = LoadExclusive(&isa.bits);
if (variant == RRVariant::FastOrMsgSend) {
// These checks are only meaningful for objc_release()
// 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()) {
swiftRelease.load(memory_order_relaxed)((id)this);
return true;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
return true;
}
}
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 false;
}
}
retry:
do {
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
return sidetable_release(sideTableLocked, performDealloc);
}
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
return false;
}
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
if (slowpath(newisa.isDeallocating()))
goto deallocate;
if (variant == RRVariant::Full) {
if (slowpath(sideTableLocked)) sidetable_unlock();
} else {
ASSERT(!sideTableLocked);
}
return false;
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 (variant != RRVariant::Full) {
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.
oldisa = LoadExclusive(&isa.bits);
goto retry;
}
// Try to remove some retain counts from the side table.
auto borrow = sidetable_subExtraRC_nolock(RC_HALF);
bool emptySideTable = borrow.remaining == 0; // we'll clear the side table if no refcounts remain there
if (borrow.borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
bool didTransitionToDeallocating = false;
newisa.extra_rc = borrow.borrowed - 1; // redo the original decrement too
newisa.has_sidetable_rc = !emptySideTable;
bool stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
if (!stored && oldisa.nonpointer) {
// 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.
uintptr_t overflow;
newisa.bits =
addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow);
newisa.has_sidetable_rc = !emptySideTable;
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
if (stored) {
didTransitionToDeallocating = newisa.isDeallocating();
}
}
}
if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
ClearExclusive(&isa.bits);
sidetable_addExtraRC_nolock(borrow.borrowed);
oldisa = LoadExclusive(&isa.bits);
goto retry;
}
// Decrement successful after borrowing from side table.
if (emptySideTable)
sidetable_clearExtraRC_nolock();
if (!didTransitionToDeallocating) {
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
}
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
deallocate:
// Really deallocate.
ASSERT(newisa.isDeallocating());
ASSERT(isa.isDeallocating());
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
- if (slowpath(isTaggedPointer())) return false;先判断是不是TaggedPointer的话,直接返回不处理。
- 在do while中
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
return sidetable_release(sideTableLocked, performDealloc);
}
判断如果不是nonpointer就执行sidetable_release这个函数,对散列表执行release操作,源码如下
uintptr_t
objc_object::sidetable_release(bool locked, bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
bool do_dealloc = false;
if (!locked) table.lock();
auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING);
auto &refcnt = it.first->second;
if (it.second) {
do_dealloc = true;
} else if (refcnt < SIDE_TABLE_DEALLOCATING) {
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
do_dealloc = true;
refcnt |= SIDE_TABLE_DEALLOCATING;
} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
refcnt -= SIDE_TABLE_RC_ONE;
}
table.unlock();
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return do_dealloc;
}
- 接着
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
return false;
}
判断是不是在析构。
- 如果是nonpointer,执行以下代码
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); //
执行-1操作,如果
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
如果减多了,跳转到这里underflow,如下
if (slowpath(newisa.has_sidetable_rc)) {
if (variant != RRVariant::Full) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
判断是否有has_sidetable_rc,如果没有跳转到
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
这时给当前对象发送一个dealloc消息。
- 如果有has_sidetable_rc,执行
auto borrow = sidetable_subExtraRC_nolock(RC_HALF);
这里代码,取一半,然后
newisa.extra_rc = borrow.borrowed - 1;
执行--操作。
- 在sidetable_subExtraRC_nolock函数中
objc_object::SidetableBorrow
objc_object::sidetable_subExtraRC_nolock(size_t delta_rc)
{
ASSERT(isa.nonpointer);
SideTable& table = SideTables()[this];
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end() || it->second == 0) {
// Side table retain count is zero. Can't borrow.
return { 0, 0 };
}
size_t oldRefcnt = it->second;
// isa-side bits should not be set here
ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT);
ASSERT(oldRefcnt > newRefcnt); // shouldn't underflow
it->second = newRefcnt;
return { delta_rc, newRefcnt >> SIDE_TABLE_RC_SHIFT };
}
散列中存一半。
3.4 dealloc简单基本流程概述
- 根据当前对象的状态是否直接调用free()释放
- 是否在瞎了眼在C++的析构函数,移除这个对象的关联属性
- 将指向该对象的弱引用指针置为nil
- 从弱引用表中移除该对象的引用计数
总结
这次我们介绍了内存的五大区,taggedPointer,retain,release的底层分析,希望些文章可以让大家对iOS的内存管理有一个新的认识,该文章略有粗糙,还望谅解。