Object-C内存管理
2020/3/25 23:01:44
本文主要是介绍Object-C内存管理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
一.内存布局
如上图,内存布局共分为如下几个区:
内核
:由系统控制处理的,大概有占有1个GB栈
:函数、方法、局部变量等会储存在这里面堆
:通过alloc分配对象、block copy...bss
:未初始化的全局变量、静态变量...data
:已初始化的全局变量、静态变量...text
: 程序代码保留
:由系统控制处理
(0xC0000000 = 3221225472 = 3GB),所以从栈区到保留区占有3GB 栈区从高地址向低地址延伸,堆区从低地址向高地址攀升 bss和data区在不区分是否初始化时,一般统称全局区 栈区内存地址:⼀般为:0x7开头 堆区内存地址:⼀般为:0x6开头 数据段,BSS内存地址:⼀般为:0x1开头
二.内存管理方案
iOS提供三种内存管理方案,TaggedPointer
,NONPOINTER_ISA
,散列表
.
1.TaggedPointer:
- ⼩对象-NSNumber,NSDate等
- 不再是一个简单的地址,而是真正的值,里面包含值,类型等等。它不再是一个对象,内存不存储在堆中,也不需要
malloc/free
- 读取速度快3倍,创建速度提升106倍。
###位运算知识补充
- (1)对同一个数值异或(^)两次,能回到原来的值(a^b^b=a)。
1010 1101 a ^ 0000 1100 b --------- 1010 0001 ^ 0000 1100 b --------- 1010 1101 a 复制代码
- (2)按位取反(~)
~100001 ------- 011110 复制代码
- (3)左移(<<)右移(>>)操作
10000111 << 3 = 10000111000 10000111 >> 3 = 10000 复制代码
- (4)位与(&)位或(|), (a | b ^ b = b)
1000 1100 a | 1010 1010 b ------------ 1010 1110 & 1010 1010 b ------------- 1010 1010 b 复制代码
源码分析
TaggedPointer
生成:
#if __has_feature(objc_fixed_enum) || __cplusplus >= 201103L enum objc_tag_index_t : uint16_t #else typedef uint16_t objc_tag_index_t; enum #endif { // 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_First60BitPayload = 0, OBJC_TAG_Last60BitPayload = 6, OBJC_TAG_First52BitPayload = 8, OBJC_TAG_Last52BitPayload = 263, OBJC_TAG_RESERVED_264 = 264 }; #if __has_feature(objc_fixed_enum) && !defined(__cplusplus) typedef enum objc_tag_index_t objc_tag_index_t; #endif static inline void * _Nonnull _objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value) { // PAYLOAD_LSHIFT and PAYLOAD_RSHIFT are the payload extraction shifts. // They are reversed here for payload insertion. // assert(_objc_taggedPointersEnabled()); if (tag <= OBJC_TAG_Last60BitPayload) { // assert(((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT) == value); uintptr_t result = (_OBJC_TAG_MASK | ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT)); return _objc_encodeTaggedPointer(result); } else { // assert(tag >= OBJC_TAG_First52BitPayload); // assert(tag <= OBJC_TAG_Last52BitPayload); // assert(((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT) == value); uintptr_t result = (_OBJC_TAG_EXT_MASK | ((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) | ((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT)); return _objc_encodeTaggedPointer(result); } } 复制代码
源码中通过对类型tag
和value
进行一些列位运算
tag << _OBJC_TAG_INDEX_SHIFT
说明最后一位是用来存储类型,
(value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT)
存储value
,
_OBJC_TAG_MASK
用来快速标记这是一个TaggedPointer
类型
然后调用_objc_encodeTaggedPointer
进行混淆,这也是为什么直接打印地址无法看出这是一个特殊地址的原因。
- 编码,解码
_objc_encodeTaggedPointer
和_objc_decodeTaggedPointer
使用的就是a^b^b=a
这个原理.
static inline void * _Nonnull _objc_encodeTaggedPointer(uintptr_t ptr) { return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr); } static inline uintptr_t _objc_decodeTaggedPointer(const void * _Nullable ptr) { return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator; } static void initializeTaggedPointerObfuscator(void) { if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) || // Set the obfuscator to zero for apps linked against older SDKs, // in case they're relying on the tagged pointer representation. DisableTaggedPointerObfuscation) { objc_debug_taggedpointer_obfuscator = 0; } else { // 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; } } 复制代码
在sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0
说明在这之前的版本objc_debug_taggedpointer_obfuscator
为0,可以直接看出地址的特殊性。单只之后的版本就无法看出了,需要手动_objc_decodeTaggedPointer
才能看到.
- 判断是否为
TaggedPointer
类型
static inline bool _objc_isTaggedPointer(const void * _Nullable ptr) { return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK; } 复制代码
通过位运算补充中的(4)a|b&b=b
可快速判断是否为TaggedPointer
TaggedPointer
取值
static inline uintptr_t _objc_getTaggedPointerValue(const void * _Nullable ptr) { // assert(_objc_isTaggedPointer(ptr)); uintptr_t value = _objc_decodeTaggedPointer(ptr); uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; if (basicTag == _OBJC_TAG_INDEX_MASK) { return (value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT; } else { return (value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT; } } 复制代码
首先进行_objc_decodeTaggedPointer
解密
然后使用和TaggedPointer
生成算法相反方式取出值.
实践
extern uintptr_t objc_debug_taggedpointer_obfuscator; int a = 10; NSString * t = [NSString stringWithFormat:@"jensen"]; NSNumber *aNum = @(a);// 64 NSLog(@"%s %p %@ 0x%lx",object_getClassName(aNum),aNum,aNum,_objc_encodeTaggedPointer(aNum)); NSLog(@"%s %p %@ 0x%lx",object_getClassName(t),t,t,_objc_encodeTaggedPointer(t)); uintptr_t _objc_encodeTaggedPointer(uintptr_t ptr) { return (objc_debug_taggedpointer_obfuscator ^ ptr); } 复制代码
打印结果:
__NSCFNumber 0xa39a2c1af54f3585 10 0xb0000000000000a3 NSTaggedPointerString 0xb39cca4dc3a96380 jensen 0xa006e65736e656a6 复制代码
总结
TaggedPointer
是通过对值和类型进行一系列位运算生成数值。通过这个数据可以快速判断类型,和获取对应的值。对小类型(NSNumber,NSDate等)将不需要在使用64位来存储,大大节省占用的内存,提高创建和访问效率。
面试题
- (void)taggedPointer_1 { dispatch_queue_t queue = dispatch_queue_create("jensen", DISPATCH_QUEUE_CONCURRENT); for (int i = 0; i<10000; i++) { dispatch_async(queue, ^{ self.nameStr = [NSString stringWithFormat:@"jensen"]; NSLog(@"%@",self.nameStr); }); } } - (void)taggedPointer_2 { dispatch_queue_t queue = dispatch_queue_create("jensen2", DISPATCH_QUEUE_CONCURRENT); for (int i = 0; i<10000; i++) { dispatch_async(queue, ^{ self.nameStr = [NSString stringWithFormat:@"大家一起搞起来"]; NSLog(@"%@",self.nameStr); }); } } 复制代码
测试结果:taggedPointer_1
运行正常,taggedPointer_2
却崩溃,什么原因?
从崩溃信息中,我们知道是释放过度导致的。
代码中self.nameStr = [NSString stringWithFormat:@"大家一起搞起来"];
,调用属性的set
方法。
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) { if (offset == 0) { object_setClass(self, newValue); return; } id oldValue; id *slot = (id*) ((char*)self + offset); if (copy) { newValue = [newValue copyWithZone:nil]; } else if (mutableCopy) { newValue = [newValue mutableCopyWithZone:nil]; } else { if (*slot == newValue) return; newValue = objc_retain(newValue); } if (!atomic) { oldValue = *slot; *slot = newValue; } else { spinlock_t& slotlock = PropertyLocks[slot]; slotlock.lock(); oldValue = *slot; *slot = newValue; slotlock.unlock(); } objc_release(oldValue); } 复制代码
从上述代码,我们知道对象赋值(set)实际上是retain/copy新值,释放(release)旧值。由于多线程操作不断的retain/release,这种情况下是不安全的。会造成对象过度释放的情况。
__attribute__((aligned(16), flatten, noinline)) id objc_retain(id obj) { if (!obj) return obj; if (obj->isTaggedPointer()) return obj; return obj->retain(); } __attribute__((aligned(16), flatten, noinline)) void objc_release(id obj) { if (!obj) return; if (obj->isTaggedPointer()) return; return obj->release(); } 复制代码
如果是TaggedPointer
类型,在retain/release
会直接retuan
,不会真正的调用对象的retain/release
。当对象赋值为jensen
属于TaggedPointer
类型,当字符串中包含有中文,或者长度比较长,TaggedPointer
无法存储,那就不是TaggedPointer
了。
2.NONPOINTER_ISA:⾮指针型isa
什么是NONPOINTER_ISA
?
我们知道在OC中,万物皆对象objc_object
。
struct objc_object { Class _Nonnull isa OBJC_ISA_AVAILABILITY; }; 复制代码
在此之前,我一直认为isa
就是仅仅只是一个指针,实例对象的isa指向类,类对象的指针指向元类。但其实isa
除包含指针外还包含其他信息,例如对象的引用计数、是否包含C++析构、是否被弱引用等等...这时这个isa就是NONPOINTER_ISA。isa是isa_t类型的联合体,其内部通过位域技术储存很多了对象的信息。
union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; # if __arm64__ # define ISA_MASK 0x0000000ffffffff8ULL # define ISA_MAGIC_MASK 0x000003f000000001ULL # define ISA_MAGIC_VALUE 0x000001a000000001ULL struct { uintptr_t nonpointer : 1; uintptr_t has_assoc : 1; uintptr_t has_cxx_dtor : 1; uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 uintptr_t magic : 6; uintptr_t weakly_referenced : 1; uintptr_t deallocating : 1; uintptr_t has_sidetable_rc : 1; uintptr_t extra_rc : 19; # define RC_ONE (1ULL<<45) # define RC_HALF (1ULL<<18) }; } 复制代码
nonpointer
:表示是否对 isa 指针开启指针优化 0:纯isa指针,1:不⽌是类对象地址,isa 中包含了类信息、对象的引⽤计数等has_assoc
:关联对象标志位,0没有,1存在has_cxx_dtor
:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象shiftcls
: 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位⽤来存储类指针。magic
:⽤于调试器判断当前对象是真的对象还是没有初始化的空间weakly_referenced
:标识对象是否被指向或者曾经指向⼀个 ARC 的弱变量, 没有弱引⽤的对象可以更快释放。deallocating
:标志对象是否正在释放内存has_sidetable_rc
:当对象引⽤技术⼤于 10 时,则需要借⽤该变量存储进位extra_rc
:表示该对象的引⽤计数值,实际上是引⽤计数值减 1, 例如,如果对象的引⽤计数为 10,那么 extra_rc 为 9。如果引⽤计数⼤于 10, 则需要使⽤到下⾯的 has_sidetable_rc。
注:当对象重写过retain,release,allocWithZone(rr/awz)
,那就不再是一个NONPOINTER_ISA
3.散列表:引⽤计数表,弱引⽤表
SideTables
是系统维护的哈希表,内部存储了一张张散列表SideTable
.每一张散列表主要用来记录对象的引用计数,弱引用对象存储等。
SideTables
SideTables
数据结构:
class StripedMap { #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR enum { StripeCount = 8 }; #else enum { StripeCount = 64 }; #endif struct PaddedT { T value alignas(CacheLineSize); }; PaddedT array[StripeCount]; static unsigned int indexForPointer(const void *p) { uintptr_t addr = reinterpret_cast<uintptr_t>(p); return ((addr >> 4) ^ (addr >> 9)) % StripeCount; } public: T& operator[] (const void *p) { return array[indexForPointer(p)].value; } const T& operator[] (const void *p) const { return const_cast<StripedMap<T>>(this)[p]; } // Shortcuts for StripedMaps of locks. void lockAll() { for (unsigned int i = 0; i < StripeCount; i++) { array[i].value.lock(); } } void unlockAll() { for (unsigned int i = 0; i < StripeCount; i++) { array[i].value.unlock(); } } void forceResetAll() { for (unsigned int i = 0; i < StripeCount; i++) { array[i].value.forceReset(); } } void defineLockOrder() { for (unsigned int i = 1; i < StripeCount; i++) { lockdebug_lock_precedes_lock(&array[i-1].value, &array[i].value); } } void precedeLock(const void *newlock) { // assumes defineLockOrder is also called lockdebug_lock_precedes_lock(&array[StripeCount-1].value, newlock); } void succeedLock(const void *oldlock) { // assumes defineLockOrder is also called lockdebug_lock_precedes_lock(oldlock, &array[0].value); } const void *getLock(int i) { if (i < StripeCount) return &array[i].value; else return nil; } #if DEBUG StripedMap() { // Verify alignment expectations. uintptr_t base = (uintptr_t)&array[0].value; uintptr_t delta = (uintptr_t)&array[1].value - base; assert(delta % CacheLineSize == 0); assert(base % CacheLineSize == 0); } #else constexpr StripedMap() {} #endif }; 复制代码
static unsigned int indexForPointer(const void *p)
对象指针通过哈希算法计算出对应的下标序号。T& operator[] (const void *p)
重写[]
操作符,可通过,&SideTables()[oldObj]
方式获取这个对象指针对应的SideTable
。lldb
调试,在SideTables
结构中获取一张SideTable
(lldb) p indexForPointer(p) (unsigned int) $4 = 4 (lldb) p array[indexForPointer(p)].value ((anonymous namespace)::SideTable) $5 = { slock = { mLock = (_os_unfair_lock_opaque = 0) } refcnts = { Buckets = 0x0000000000000000 NumEntries = 0 NumTombstones = 0 NumBuckets = 0 } weak_table = { weak_entries = 0x0000000000000000 num_entries = 0 mask = 0 max_hash_displacement = 0 } } 复制代码
SideTable
SideTable
内部数据结构:
struct SideTable { spinlock_t slock; RefcountMap refcnts; weak_table_t weak_table; ... }; 复制代码
spinlock_t slock
自旋锁,用于控制SideTable
的访问安全.
refcnts
引用计数表,是一个Map
,用于存储引用计数,具体下面会展开讲解。
weak_table
弱引用表.
疑问
1.SidleTables
是一张哈希表,内部存了多张散列表。为什么需要使用多张?
答:对SidleTable
操作时,需要进行加锁、解锁。频繁操作,会降低性能。多张表可以分开加锁,提高效率。
2.为什么不是一个类对应一个SidleTable
?
创建SidleTable
和管理SidleTable
都需要耗费性能,所以几个类共用一个SidleTable
三.引用计数
1.alloc
出来的引用技术是多少?
2.对象在什么时候会调用Dealloc
?
3.引用计数在什么时候会加,减?
4.引用计数存在哪?
5.dealloc底层,应该做一些什么事情?
带着上面几个问题,我们展开对源码的分析。引用计数的核心就是对象的retain
、release
,因此首先从这2个函数入手分析:
retain
-(id) retain { return _objc_rootRetain(self); } id _objc_rootRetain(id obj) { assert(obj); return obj->rootRetain(); } objc_object::rootRetain() { return rootRetain(false, false); } objc_object::rootRetain(bool tryRetain, bool handleOverflow) { //1. isTaggedPointer 直接返回 if (isTaggedPointer()) return (id)this; //2.用于标记锁的状态 bool sideTableLocked = false; //3.标记是否需要装到到 bool transcribeToSideTable = false; isa_t oldisa; isa_t newisa; do { transcribeToSideTable = false; oldisa = LoadExclusive(&isa.bits); newisa = oldisa; //4.不是nonpointer if (slowpath(!newisa.nonpointer)) { ClearExclusive(&isa.bits); if (!tryRetain && sideTableLocked) sidetable_unlock(); if (tryRetain) return sidetable_tryRetain() ? (id)this : nil; //5.不是nonpointer类型,跳转nonpointer else return sidetable_retain(); } // don't check newisa.fast_rr; we already called any RR overrides //6.析构,返回nil if (slowpath(tryRetain && newisa.deallocating)) { ClearExclusive(&isa.bits); if (!tryRetain && sideTableLocked) sidetable_unlock(); return nil; } //7.进位标记 uintptr_t carry; //8.extra_rc++ newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ if (slowpath(carry)) { //9. 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. if (!tryRetain && !sideTableLocked) sidetable_lock(); sideTableLocked = true; transcribeToSideTable = true; //10.溢出时extra_rc保存一把 newisa.extra_rc = RC_HALF; 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; } id objc_object::sidetable_retain() { #if SUPPORT_NONPOINTER_ISA assert(!isa.nonpointer); #endif SideTable& table = SideTables()[this]; table.lock(); size_t& refcntStorage = table.refcnts[this]; if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { refcntStorage += SIDE_TABLE_RC_ONE; } table.unlock(); return (id)this; } 复制代码
- 1.
TaggedPointer
类型,直接return
- 2.不是
nonpointer
类型,调用sidetable_retain
,对引用计数表数值+1 - 3.
nonpointer
类型,extra_rc++
,判断是否溢出,溢出时,extra_rc存储RC_HALF(RC_HALF)
的引用计数,另一半存储值散列表的引用技术表。
release
和retain
类似,此处就不再贴源码.
- 1.
TaggedPointer
类型,直接return
- 2.不是
nonpointer
类型,调用sidetable_retain
,对引用计数表数值-1 - 3.
nonpointer
类型,extra_rc--
,判断是否下溢出 - 4.当下溢出时,判断散列表是否还有值,如果有就从散列表借,extra_rc存储
RC_HALF(RC_HALF)
引用计数. - 如果散列表也没有了,那就标记
deallocating
为true,并发送dealloc
消息.
retainCount()
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) { 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(); } 复制代码
- 1.
TaggedPointer
返回的是(uintptr_t)this
-
nonpointer
返回的是1 + bits.extra_rc
,如果引用计数表有值,还需要加上引用计数表的存储值
-
- 非
nonpointer
,返回计数表的存储值
- 非
dealloc
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); } 复制代码
- 1.TaggedPointer,直接return
- 2.
isa.nonpointer && !isa.weakly_referenced && !isa.has_assoc && !isa.has_cxx_dtor && !isa.has_sidetable_rc
,直接释放 - 3.存在析构函数、关联对象,都需要移除
- 4.在引用计数表中檫除对象,弱引用表设置为Nil
- 5.释放
总结:通过对retain
,release
,retainCount
,dealloc
源码分析,上述5个问题均可以在里面找到答案。此处就不在赘述。
四.弱引用weak
1.弱引用对象是如何加入弱引用计数? 2.对象析构时,对象弱引用表中的对象如何设置为nil?
NSObject * n = [[NSObject alloc] init]; __weak NSObject *weakN = n; 复制代码lldb调试得出,声明以为
weak
变量首先会执行objc_initWeak
函数,因此我们从此处入手进行分析。
id objc_initWeak(id *location, id newObj) { if (!newObj) { *location = nil; return nil; } return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj); } 复制代码
newObj
不存在,直接return,否则调用storeWeak
static id storeWeak(id *location, objc_object *newObj) { assert(haveOld || haveNew); if (!haveNew) assert(newObj == nil); Class previouslyInitializedClass = nil; id oldObj; SideTable *oldTable; SideTable *newTable; // Acquire locks for old and new values. // Order by lock address to prevent lock ordering problems. // Retry if the old value changes underneath us. retry: if (haveOld) { oldObj = *location; oldTable = &SideTables()[oldObj]; } else { oldTable = nil; } if (haveNew) { newTable = &SideTables()[newObj]; } else { newTable = nil; } SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable); if (haveOld && *location != oldObj) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); goto retry; } // Prevent a deadlock between the weak reference machinery // and the +initialize machinery by ensuring that no // weakly-referenced object has an un-+initialized isa. if (haveNew && newObj) { Class cls = newObj->getIsa(); if (cls != previouslyInitializedClass && !((objc_class *)cls)->isInitialized()) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); class_initialize(cls, (id)newObj); // If this class is finished with +initialize then we're good. // If this class is still running +initialize on this thread // (i.e. +initialize called storeWeak on an instance of itself) // then we may proceed but it will appear initializing and // not yet initialized to the check above. // Instead set previouslyInitializedClass to recognize it on retry. previouslyInitializedClass = cls; goto retry; } } // Clean up old value, if any. if (haveOld) { weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); } // Assign new value, if any. if (haveNew) { newObj = (objc_object *) weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating); // weak_register_no_lock returns nil if weak store should be rejected // Set is-weakly-referenced bit in refcount table. if (newObj && !newObj->isTaggedPointer()) { newObj->setWeaklyReferenced_nolock(); } // Do not set *location anywhere else. That would introduce a race. *location = (id)newObj; } else { // No new value. The storage is not changed. } SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); return (id)newObj; } 复制代码
- 如果存在旧值,调用
weak_unregister_no_lock
处理。
void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id) { objc_object *referent = (objc_object *)referent_id; objc_object **referrer = (objc_object **)referrer_id; weak_entry_t *entry; if (!referent) return; if ((entry = weak_entry_for_referent(weak_table, referent))) { remove_referrer(entry, referrer); bool empty = true; if (entry->out_of_line() && entry->num_refs != 0) { empty = false; } else { for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i]) { empty = false; break; } } } if (empty) { weak_entry_remove(weak_table, entry); } } // Do not set *referrer = nil. objc_storeWeak() requires that the // value not change. } 复制代码
- 首先调用
weak_entry_for_referent
从waek_table
中获取entry
- 然后调用
remove_referrer
,在entry
的referrers
中找到地址的索引,entry->referrers[index] = nil;entry->num_refs--;
设置为nil,并将num_refs
减1 - 判断
entry
是否还有值,没有就在weak_table
移除这个entry
- 如果不存在旧值,调用
weak_register_no_lock
// now remember it and where it is being stored weak_entry_t *entry; if ((entry = weak_entry_for_referent(weak_table, referent))) { append_referrer(entry, referrer); } else { weak_entry_t new_entry(referent, referrer); weak_grow_maybe(weak_table); weak_entry_insert(weak_table, &new_entry); 复制代码
(entry = weak_entry_for_referent(weak_table, referent))
获取entry
(1)entry
存在,调用append_referrer
,将new_referrer
添加到entry->referrers
将new_referrer
先赋值到entry->inline_referrers[i]
然后将entry->inline_referrers
循环对应拷贝到new_referrers
将new_referrers
赋值给entry->referrers = new_referrers;
(2)entry
不存在,
创建⼀个weak_entry_t
把referent
加⼊到weak_entry_t
的数组inline_referrers
,``
把weak_table
扩容,weak_grow_maybe(weak_table)
把new_entry
加⼊到weak_table
中.weak_entry_insert(weak_table, &new_entry);
在三.引用计数的dealloc中,我们知道,对象在析构(deealloc)时,如果存在弱引用对象:
... SideTable& table = SideTables()[this]; table.lock(); if (isa.weakly_referenced) { weak_clear_no_lock(&table.weak_table, (id)this); } //在引用技术标中,移除这个对象。 if (isa.has_sidetable_rc) { table.refcnts.erase(this); } table.unlock() ... 复制代码
存在弱引用对象,调用weak_clear_no_lock
void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) { objc_object *referent = (objc_object *)referent_id; weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); if (entry == nil) { /// XXX shouldn't happen, but does with mismatched CF/objc //printf("XXX no entry for clear deallocating %p\n", referent); return; } // zero out references weak_referrer_t *referrers; size_t count; if (entry->out_of_line()) { referrers = entry->referrers; count = TABLE_SIZE(entry); } else { referrers = entry->inline_referrers; count = WEAK_INLINE_COUNT; } for (size_t i = 0; i < count; ++i) { objc_object **referrer = referrers[i]; if (referrer) { if (*referrer == referent) { *referrer = nil; } else if (*referrer) { _objc_inform("__weak variable at %p holds %p instead of %p. " "This is probably incorrect use of " "objc_storeWeak() and objc_loadWeak(). " "Break on objc_weak_error to debug.\n", referrer, (void*)*referrer, (void*)referent); objc_weak_error(); } } } weak_entry_remove(weak_table, entry); } 复制代码
- 在
weak_table
获取对象的entry
- 循环
entry
下的referrers
,将其指向设置为nil,*referrer = nil;
weak_table
中移除entry
五.变量修饰符
变量修饰符有一下几种情况:
typedef enum { objc_ivar_memoryUnknown, // unknown / unknown objc_ivar_memoryStrong, // direct access / objc_storeStrong objc_ivar_memoryWeak, // objc_loadWeak[Retained] / objc_storeWeak objc_ivar_memoryUnretained // direct access / direct access } objc_ivar_memory_management_t; 复制代码
通过源码分析变量不同修饰符的setter
方法的处理:
void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong) { if (!obj || !ivar || obj->isTaggedPointer()) return; ptrdiff_t offset; objc_ivar_memory_management_t memoryManagement; _class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement); if (memoryManagement == objc_ivar_memoryUnknown) { if (assumeStrong) memoryManagement = objc_ivar_memoryStrong; else memoryManagement = objc_ivar_memoryUnretained; } id *location = (id *)((char *)obj + offset); switch (memoryManagement) { case objc_ivar_memoryWeak: objc_storeWeak(location, value); break; case objc_ivar_memoryStrong: objc_storeStrong(location, value); break; case objc_ivar_memoryUnretained: *location = value; break; case objc_ivar_memoryUnknown: _objc_fatal("impossible"); } } 复制代码
TaggedPointer
类型,直接return
- 获取内存修饰符
objc_ivar_memory_management_t
._class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement)
(1)objc_ivar_memoryWeak
,调用objc_storeWeak
操作弱引用表,上述已经分析过. (2)objc_ivar_memoryStrong
,调用objc_storeStrong
,retain新值,释放旧值 (3)objc_ivar_memoryUnretained
,直接将value存储至*location
。这也说明为什么Unretained
是不安全的。
六.自动释放池AutoReleasePool
自动释放池介绍
AutoReleasePool
是ARC
引入的,用于管理对象的引用计数。
以下是AutoReleasePool
的几个要点:
- 一个线程的自动释放池是一种栈形式的指针集合,先进后出;
- 每个指针要么是要释放的对象,要么是池的边界,即自动释放池边界;
- 池token是指向该池边界的指针。当池被弹出时,所有比哨兵还热的对象都被释放;
- 这个栈是一个双向链表的页面列表。根据需要添加和删除页面。
- 线程本地存储指向热页,其中存储新的自动释放的对象。
AutoReleasePool
结构图:
AutoReleasePool
数据结构:
class AutoreleasePoolPage; struct AutoreleasePoolPageData { magic_t const magic; // 16 __unsafe_unretained id *next; //8 pthread_t const thread; // 8 //证明了双向链表结构 AutoreleasePoolPage * const parent; //8 AutoreleasePoolPage *child; //8 uint32_t const depth; // 4 uint32_t hiwat; // 4 AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat) : magic(), next(_next), thread(_thread), parent(_parent), child(nil), depth(_depth), hiwat(_hiwat) { } }; 复制代码
AutoreleasePoolPage
是个继承于AutoreleasePoolPageData
结构体的类,objc4-779.1
版本开始独立出AutoreleasePoolPageData
结构体,之前变量是直接在AutoreleasePoolPage
中。magic_t const magic
:用来校验AutoreleasePoolPage
的结构是否完整__unsafe_unretained id *next
: 指向最新添加的autorelease对象的下一个位置,初始化时指向begin()pthread_t const thread
:当前线程AutoreleasePoolPage * const parent
:指向父节点,第一个parent节点为nilAutoreleasePoolPage *child
:指向子节点,最后一个child节点为niluint32_t const depth
:代表深度,从0开始,递增+1uint32_t hiwat
:代表 high water Mark 最大入栈数量标记
自动释放池探索
使用clang -rewrite-objc main.m -o main.cpp
编译如下代码:
int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"Jensen"); } return 0; } 复制代码
编译结果:
int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; NSLog((NSString *)&__NSConstantStringImpl__var_folders_v7_6tlrq64x5w5gqg17582f4p500000gn_T_main_3f39be_mi_0); } return 0; } struct __AtAutoreleasePool { __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();} ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);} void * atautoreleasepoolobj; }; 复制代码
@autoreleasepool{}
实际上是实例化__AtAutoreleasePool
,在构造方法中调用objc_autoreleasePoolPush
atautoreleasepoolobj = objc_autoreleasePoolPush();
static inline void *push() { id *dest; if (slowpath(DebugPoolAllocation)) { // Each autorelease pool starts on a new pool page. dest = autoreleaseNewPage(POOL_BOUNDARY); } else { dest = autoreleaseFast(POOL_BOUNDARY); } ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; } 复制代码
- 通过环境变量
OBJC_DEBUG_POOL_ALLOCATION
判断自动释放池是否被允许跟踪调试,如果允许调用autoreleaseNewPage
,否则进入autoreleaseFast
.此处,我们分析autoreleaseFast
。 - 自动释放池初始化,会调用
objc_autoreleasePoolPush
static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); if (page && !page->full()) { return page->add(obj); } else if (page) { return autoreleaseFullPage(obj, page); } else { return autoreleaseNoPage(obj); } } 复制代码
- 获取当前
AutoreleasePoolPage
的hotPage
- 存在
hotPage
,并且未满,直接调用page->add(obj)
将对象添加到AutoreleasePoolPage
- 存在
hotPage
,但是已满,调用autoreleaseFullPage
- 没有
hotPage
,说明是第一次加入,调用autoreleaseNoPage
id *add(id obj) { ASSERT(!full()); unprotect(); id *ret = next; // faster than `return next-1` because of aliasing *next++ = obj; protect(); return ret; } 复制代码
将对象加入到hotPage
中.
static __attribute__((noinline)) id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) { // The hot page is full. // Step to the next non-full page, adding a new page if necessary. // Then add the object to that page. ASSERT(page == hotPage()); ASSERT(page->full() || DebugPoolAllocation); do { if (page->child) page = page->child; else page = new AutoreleasePoolPage(page); } while (page->full()); setHotPage(page); return page->add(obj); } 复制代码
循环找到最后一页,当前page作为父page创建一个新的AutoreleasePoolPage
,将新创建的page设置为hotPage
,调用add
将对象加入到新page
中.
static __attribute__((noinline)) id *autoreleaseNoPage(id obj) { // "No page" could mean no pool has been pushed // or an empty placeholder pool has been pushed and has no contents yet ASSERT(!hotPage()); bool pushExtraBoundary = false; if (haveEmptyPoolPlaceholder()) { // We are pushing a second pool over the empty placeholder pool // or pushing the first object into the empty placeholder pool. // Before doing that, push a pool boundary on behalf of the pool // that is currently represented by the empty placeholder. pushExtraBoundary = true; } else if (obj != POOL_BOUNDARY && DebugMissingPools) { // We are pushing an object with no pool in place, // and no-pool debugging was requested by environment. _objc_inform("MISSING POOLS: (%p) Object %p of class %s " "autoreleased with no pool in place - " "just leaking - break on " "objc_autoreleaseNoPool() to debug", objc_thread_self(), (void*)obj, object_getClassName(obj)); objc_autoreleaseNoPool(obj); return nil; } else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) { // We are pushing a pool with no pool in place, // and alloc-per-pool debugging was not requested. // Install and return the empty pool placeholder. return setEmptyPoolPlaceholder(); } // We are pushing an object or a non-placeholder'd pool. // Install the first page. AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); setHotPage(page); // Push a boundary on behalf of the previously-placeholder'd pool. if (pushExtraBoundary) { page->add(POOL_BOUNDARY); } // Push the requested object or pool. return page->add(obj); } 复制代码
会直接创建第一个page
,并将这个page
设置为hotPage
,然后加入边界符POOL_BOUNDARY
用_objc_autoreleasePoolPrint();
打印一个空的自动释放池:
一张page占用4096
字节,从图中我们知道page属性占用56(3 * 16 + 8)
字节,一个page能容纳505((4096 - 56)/8 = 505)
个对象,第一页包含POOL
的特殊边界符,占用1
个对象,因此第一页能容纳504个
对象和1
个特殊标记符,其他页面能容纳505
个对象。
objc_autoreleasePoolPop
void _objc_autoreleasePoolPop(void *ctxt) { objc_autoreleasePoolPop(ctxt); } void objc_autoreleasePoolPop(void *ctxt) { AutoreleasePoolPage::pop(ctxt); } static inline void pop(void *token) { AutoreleasePoolPage *page; id *stop; if (token == (void*)EMPTY_POOL_PLACEHOLDER) { // Popping the top-level placeholder pool. page = hotPage(); if (!page) { // Pool was never used. Clear the placeholder. return setHotPage(nil); } // Pool was used. Pop its contents normally. // Pool pages remain allocated for re-use as usual. page = coldPage(); token = page->begin(); } else { page = pageForPointer(token); } stop = (id *)token; if (*stop != POOL_BOUNDARY) { if (stop == page->begin() && !page->parent) { // Start of coldest page may correctly not be POOL_BOUNDARY: // 1. top-level pool is popped, leaving the cold page in place // 2. an object is autoreleased with no pool } else { // Error. For bincompat purposes this is not // fatal in executables built with old SDKs. return badPop(token); } } if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) { return popPageDebug(token, page, stop); } return popPage<false>(token, page, stop); } 复制代码
- 自动释放池析构时,调用
_objc_autoreleasePoolPop
token
指定需要释放到的位置- 找到
token
对应的page popPage<false>(token, page, stop);
开始pop
template<bool allowDebug> static void popPage(void *token, AutoreleasePoolPage *page, id *stop) { if (allowDebug && PrintPoolHiwat) printHiwat(); page->releaseUntil(stop); // memory: delete empty children if (allowDebug && DebugPoolAllocation && page->empty()) { // special case: delete everything during page-per-pool debugging AutoreleasePoolPage *parent = page->parent; page->kill(); setHotPage(parent); } else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) { // special case: delete everything for pop(top) // when debugging missing autorelease pools page->kill(); setHotPage(nil); } else if (page->child) { // hysteresis: keep one empty child if page is more than half full if (page->lessThanHalfFull()) { page->child->kill(); } else if (page->child->child) { page->child->child->kill(); } } } 复制代码
page->releaseUntil(stop);
释放对象- page为空,直接释放这个page,如果有child,将child也kill
void releaseUntil(id *stop) { // Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage while (this->next != stop) { // Restart from hotPage() every time, in case -release // autoreleased more objects AutoreleasePoolPage *page = hotPage(); // fixme I think this `while` can be `if`, but I can't prove it while (page->empty()) { page = page->parent; setHotPage(page); } page->unprotect(); id obj = *--page->next; memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); page->protect(); if (obj != POOL_BOUNDARY) { objc_release(obj); } } setHotPage(this); #if DEBUG // we expect any children to be completely empty for (AutoreleasePoolPage *page = child; page; page = page->child) { ASSERT(page->empty()); } #endif } 复制代码
- 循环遍历,取出对象,并释放。
总结:
当要pop
对象的时候,系统给一个token
对象指针,这个指针用于指定释放的程度
找到token
对象所在的page
,并生成一个stop
停止对象,然后开始pop
操作
page->releaseUntil(stop)
,内部循环遍历执行对象的release
,直到stop
对象,并将当前page
设为hotpage
将已经释放对象所属的page杀了,即删除空的child page.
autorelease
前面已经介绍了objc_autoreleasePoolPush
和objc_autoreleasePoolPop
,接下来我们看看autorelease
又做了什么.
static inline id autorelease(id obj) { ASSERT(obj); ASSERT(!obj->isTaggedPointer()); id *dest __unused = autoreleaseFast(obj); ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj); return obj; } static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); if (page && !page->full()) { return page->add(obj); } else if (page) { return autoreleaseFullPage(obj, page); } else { return autoreleaseNoPage(obj); } } 复制代码
autorelease
的实现和objc_autoreleasePoolPush
类似,这里就不在赘述了。
自动释放池、RunLoop
-
App启动后,苹果在主线程
RunLoop
里注册了两个Observer
,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()
。 -
第一个
Observer
监视的事件是 Entry(即将进入Loop),其回调内会调用_objc_autoreleasePoolPush()
创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。 -
第二个
Observer
监视了两个事件:BeforeWaiting(准备进入休眠)
时调用_objc_autoreleasePoolPop()
和_objc_autoreleasePoolPush()
释放旧的池并创建新池;Exit(即将退出Loop)
时调用_objc_autoreleasePoolPop()
来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。 -
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool了。
-
一个线程只有一个
autoreleasePool
-
autoreleasePool
嵌套时,只会创建一个page
,但是有两个池边界
observers = ( "<CFRunLoopObserver 0x600001238280 [0x10b19ab68]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10dd891b1), context = <CFArray 0x600002d3c1b0 [0x10b19ab68]>{type = mutable-small, count = 0, values = ()}}", "<CFRunLoopObserver 0x60000123c500 [0x10b19ab68]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x10d95b473), context = <CFRunLoopObserver context 0x60000083cfc0>}", "<CFRunLoopObserver 0x600001238140 [0x10b19ab68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x10ddb8dfc), context = <CFRunLoopObserver context 0x7fdae6d020c0>}", "<CFRunLoopObserver 0x6000012381e0 [0x10b19ab68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x10ddb8e75), context = <CFRunLoopObserver context 0x7fdae6d020c0>}", "<CFRunLoopObserver 0x600001238320 [0x10b19ab68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10dd891b1), context = <CFArray 0x600002d3c1b0 [0x10b19ab68]>{type = mutable-small, count = 0, values = ()}}" ), 复制代码
这篇关于Object-C内存管理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值