前言 之前的文章有说过 Atomic
原子操作的原理,其作为一个特殊的修饰前缀,影响了存取操作。
在属性修饰定义中,还有另一类修饰前缀,他们分别是 strong
weak
assign
copy
,这些又有什么区别呢?
平时喜欢探究的同学,可能也见过 unsafe_unretained
,这个又是什么呢?
让我们从属性修饰入手,逐步揭开弱引用的面纱。
原理 属性自动生成的实现方法是怎么样的? 首先我们先创建一个示例代码文件作为样本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #import <Foundation/Foundation.h> @interface PropertyObject : NSObject @property (nonatomic , strong ) NSObject *pStrongObj; @property (nonatomic , copy ) NSObject *pCopyObj; @property (nonatomic , weak ) NSObject *pWeakObj; @property (nonatomic , assign ) NSObject *pAssignObj; @property (nonatomic , unsafe_unretained ) NSObject *pUnretainedObj; @end @implementation PropertyObject @end
然后通过 clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.14 -fobjc-runtime=macosx-10.14 -Wno-deprecated-declarations main.m
命令将其解释成 c++
代码。(注意这里要指定版本,不然weak属性不能翻译)
展开的代码比较多,我这里截取关键部分探讨。
1 2 3 4 5 6 7 8 9 10 11 12 13 struct PropertyObject_IMPL { NSObject *__strong _pStrongObj; NSObject *__strong _pCopyObj; NSObject *__weak _pWeakObj; NSObject *__unsafe_unretained _pAssignObj; NSObject *__unsafe_unretained _pUnretainedObj; }; {"pStrongObj" ,"T@\"NSObject\",&,N,V_pStrongObj" }, {"pCopyObj" ,"T@\"NSObject\",C,N,V_pCopyObj" }, {"pWeakObj" ,"T@\"NSObject\",W,N,V_pWeakObj" }, {"pAssignObj" ,"T@\"NSObject\",N,V_pAssignObj" }, {"pUnretainedObj" ,"T@\"NSObject\",N,V_pUnretainedObj" }
从变量结构体的描述和特性可以看出,strong
和copy
实际都是__strong
修饰,但特性不同,assign
和unsafe_unretained
则完全一致,都是__unsafe_unretained
,weak
则单独使用__weak
修饰。
下面我们来看一下方法具体实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static NSObject * _I_PropertyObject_pStrongObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_PropertyObject$_pStrongObj)); }static void _I_PropertyObject_setPStrongObj_(PropertyObject * self, SEL _cmd, NSObject *pStrongObj) { (*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_PropertyObject$_pStrongObj)) = pStrongObj; }static NSObject * _I_PropertyObject_pCopyObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_PropertyObject$_pCopyObj)); }extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long , id, bool , bool ) ;static void _I_PropertyObject_setPCopyObj_(PropertyObject * self, SEL _cmd, NSObject *pCopyObj) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct PropertyObject, _pCopyObj), (id)pCopyObj, 0 , 1 ); }static NSObject * _I_PropertyObject_pWeakObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__weak *)((char *)self + OBJC_IVAR_$_PropertyObject$_pWeakObj)); }static void _I_PropertyObject_setPWeakObj_(PropertyObject * self, SEL _cmd, NSObject *pWeakObj) { (*(NSObject *__weak *)((char *)self + OBJC_IVAR_$_PropertyObject$_pWeakObj)) = pWeakObj; }static NSObject * _I_PropertyObject_pAssignObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pAssignObj)); }static void _I_PropertyObject_setPAssignObj_(PropertyObject * self, SEL _cmd, NSObject *pAssignObj) { (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pAssignObj)) = pAssignObj; }static NSObject * _I_PropertyObject_pUnretainedObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pUnretainedObj)); }static void _I_PropertyObject_setPUnretainedObj_(PropertyObject * self, SEL _cmd, NSObject *pUnretainedObj) { (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pUnretainedObj)) = pUnretainedObj; }
在代码中,只有copy
修饰属性的setter
方法使用了objc_setProperty
,其他几种都是根据 self + 偏移量
的方式计算出内存地址直接进行存取。
那问题来了,如果真的是那么简单的话,arc
是怎么实现根据不同修饰从而进行内存管理的呢?
原来通过 clang -rewrite-objc
的代码只是翻译成 c++
语言,在之后的编译过程中会进一步处理。
接着使用 clang -S -fobjc-arc -emit-llvm main.m -o main.ll
命令生成中间码。
(中间码显示比较杂乱,我根据自己理解整理成简洁版)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 //代码整理后 id [PropertyObject pStrongObj] { return *location; } void [PropertyObject setPStrongObj:](self, _cmd, obj) { @llvm.objc.storeStrong(*location, obj) } id [PropertyObject pCopyObj] { return @objc_getProperty(self, _cmd, offset, atomic) } void [PropertyObject setPCopyObj:](self, _cmd, obj) { @objc_setProperty_nonatomic_copy(self, _cmd, obj, offset) } id [PropertyObject pWeakObj] { id obj = @llvm.objc.loadWeakRetained(*location) return @llvm.objc.autoreleaseReturnValue(obj) } void [PropertyObject setPWeakObj:](self, _cmd, obj) { @llvm.objc.storeWeak(*location, obj) } id [PropertyObject pAssignObj] { return *location } void [PropertyObject setPAssignObj:](self, _cmd, obj) { *location = obj } id [PropertyObject pUnretainedObj] { return *location } void [PropertyObject setPUnretainedObj:](self, _cmd, obj) { *location = obj }
可以看出分别针对strong
和 weak
都做了处理,而assign
和 unsafe_unretained
则不做内存管理直接返回,这也说明这两者的处理方式是一样的,区别在于 assign
针对。
strong
copy
weak
assign
unsafe_unretained
Ownership
__strong
__strong
__weak
__unsafe_unretained
__unsafe_unretained
Getter
*location
objc_getProperty
loadWeakRetained
*location
*location
Setter
storeStrong
objc_setProperty
storeWeak
*location
*location
对象
NSObject
NSObject
NSObject
NSObject
Scalar
Weak对象怎么实现存取的? 本文篇幅有限,暂不介绍 storeStrong
和 objc_setProperty_nonatomic_copy
,主要介绍 weak
相关操作。
打开 objc4-750
开源代码,翻到 NSObject.mm
,我们来一探究竟。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 id objc_initWeak (id *location, id newObj) { if (!newObj) { *location = nil; return nil; } return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj); } void objc_destroyWeak (id *location) { (void )storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating> (location, nil); } id objc_storeWeak (id *location, id newObj) { return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object *)newObj); }
可以看到 runtime
中调用的都是一个方法,区别在于使用了不同的模版,那么我们来看下对一个地址的存取方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 id objc_loadWeakRetained (id *location) { id obj; id result; Class cls; SideTable *table; retry: obj = *location; if (!obj) return nil; if (obj->isTaggedPointer()) return obj; table = &SideTables()[obj]; table->lock(); if (*location != obj) { table->unlock(); goto retry; } result = obj; cls = obj->ISA(); if (! cls->hasCustomRR()) { assert(cls->isInitialized()); if (! obj->rootTryRetain()) { result = nil; } } else { if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) { BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL)) class_getMethodImplementation(cls, SEL_retainWeakReference); if ((IMP)tryRetain == _objc_msgForward) { result = nil; } else if (! (*tryRetain)(obj, SEL_retainWeakReference)) { result = nil; } } else { table->unlock(); _class_initialize(cls); goto retry; } } table->unlock(); return result; } 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; 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; } if (haveNew && newObj) { Class cls = newObj->getIsa(); if (cls != previouslyInitializedClass && !((objc_class *)cls)->isInitialized()) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); _class_initialize(_class_getNonMetaClass(cls, (id)newObj)); previouslyInitializedClass = cls; goto retry; } } if (haveOld) { weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); } if (haveNew) { newObj = (objc_object *) weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating); if (newObj && !newObj->isTaggedPointer()) { newObj->setWeaklyReferenced_nolock(); } *location = (id)newObj; } SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); return (id)newObj; }
除去保护方法,其实 objc_loadWeakRetained
方法就是检查后返回 *location
,也就是变量指向的实际地址。
而 storeWeak
方法则是根据模版,对旧对象执行 weak_unregister_no_lock
,对新对象执行 weak_register_no_lock
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 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); } } } id weak_register_no_lock (weak_table_t *weak_table, id referent_id, id *referrer_id, bool crashIfDeallocating) { objc_object *referent = (objc_object *)referent_id; objc_object **referrer = (objc_object **)referrer_id; if (!referent || referent->isTaggedPointer()) return referent_id; bool deallocating; if (!referent->ISA()->hasCustomRR()) { deallocating = referent->rootIsDeallocating(); } else { BOOL (*allowsWeakReference)(objc_object *, SEL) = (BOOL(*)(objc_object *, SEL)) object_getMethodImplementation((id)referent, SEL_allowsWeakReference); if ((IMP)allowsWeakReference == _objc_msgForward) { return nil; } deallocating = ! (*allowsWeakReference)(referent, SEL_allowsWeakReference); } if (deallocating) { if (crashIfDeallocating) { _objc_fatal("Cannot form weak reference to instance (%p) of " "class %s. It is possible that this object was " "over-released, or is in the process of deallocation." , (void *)referent, object_getClassName((id)referent)); } else { return nil; } } 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); } return referent_id; } 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) { return ; } 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
的变量进行存取操作,其实都是通过被操作的对象地址查找到相应的表,然后增删表的引用数组内容。
SideTable表怎么设计的? 关键就在于怎么申明创建表,以及这个表是怎么设计及使用的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 alignas(StripedMap<SideTable>) static uint8_t SideTableBuf[sizeof (StripedMap<SideTable>)]; static void SideTableInit () { new (SideTableBuf) StripedMap<SideTable>(); } static StripedMap<SideTable>& SideTables() { return *reinterpret_cast <StripedMap<SideTable>*>(SideTableBuf); } enum { CacheLineSize = 64 };template <typename T>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; } }
在加载镜像的过程中,通过 SideTableInit
方法创建全局表数组,可以看到手机系统是8个数组。
源码中使用 &SideTables()[obj]
的方式,其实就是把 obj
的指针地址转成序号获取某一个 table
,通过这种方式分散冗余。
接着我们看 SideTable
类的内部结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t ,true > RefcountMap;enum HaveOld { DontHaveOld = false , DoHaveOld = true };enum HaveNew { DontHaveNew = false , DoHaveNew = true };struct SideTable { spinlock_t slock; RefcountMap refcnts; weak_table_t weak_table; template <HaveOld, HaveNew> static void lockTwo (SideTable *lock1, SideTable *lock2) ; template <HaveOld, HaveNew> static void unlockTwo (SideTable *lock1, SideTable *lock2) ; }; struct weak_table_t { weak_entry_t *weak_entries; size_t num_entries; uintptr_t mask; uintptr_t max_hash_displacement; }; #if __LP64__ #define PTR_MINUS_2 62 #else #define PTR_MINUS_2 30 #endif typedef DisguisedPtr<objc_object *> weak_referrer_t ;struct weak_entry_t { DisguisedPtr<objc_object> referent; union { struct { weak_referrer_t *referrers; uintptr_t out_of_line_ness : 2 ; uintptr_t num_refs : PTR_MINUS_2; uintptr_t mask; uintptr_t max_hash_displacement; }; struct { weak_referrer_t inline_referrers[4 ]; }; }; };
SideTable
存储的不仅有对象引用计数表,还有我们关注的弱引用表,其结构顺序如下:
SideTable->weak_table_t->weak_entry_t->weak_referrer_t
为了方便理解,我模拟一下找弱引用对象的步骤:
sideTable = &SideTables()[referent]
把对象内存地址按照8取余后找到表
weakTable = &sideTable->weak_table
取出弱引用表
entry = weak_entry_for_referent(weakTable, referent)
根据被引用人地址,遍历弱引用表找出入口
referrer = entry->referrers[index]
入口有特殊的数组,其中保存了所有弱引用者的对象地址
仔细一点的同学应该发现了 weak_entry_t
中有一个联合体,这又是怎么操作实现的呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 static void append_referrer (weak_entry_t *entry, objc_object **new_referrer) { if (! entry->out_of_line()) { for (size_t i = 0 ; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i] == nil) { entry->inline_referrers[i] = new_referrer; return ; } } weak_referrer_t *new_referrers = (weak_referrer_t *) calloc (WEAK_INLINE_COUNT, sizeof (weak_referrer_t )); for (size_t i = 0 ; i < WEAK_INLINE_COUNT; i++) { new_referrers[i] = entry->inline_referrers[i]; } entry->referrers = new_referrers; entry->num_refs = WEAK_INLINE_COUNT; entry->out_of_line_ness = REFERRERS_OUT_OF_LINE; entry->mask = WEAK_INLINE_COUNT-1 ; entry->max_hash_displacement = 0 ; } assert(entry->out_of_line()); if (entry->num_refs >= TABLE_SIZE(entry) * 3 /4 ) { return grow_refs_and_insert(entry, new_referrer); } size_t begin = w_hash_pointer(new_referrer) & (entry->mask); size_t index = begin; size_t hash_displacement = 0 ; while (entry->referrers[index] != nil) { hash_displacement++; index = (index+1 ) & entry->mask; if (index == begin) bad_weak_table(entry); } if (hash_displacement > entry->max_hash_displacement) { entry->max_hash_displacement = hash_displacement; } weak_referrer_t &ref = entry->referrers[index]; ref = new_referrer; entry->num_refs++; }
总结 至此对于弱引用的整体结构和逻辑都清楚了,对象根据修饰符进行内存管理,如果是弱引用,则找到其引用地址的引用表操作。
反过来讲,强对象被引用时在全局引用表中注册一个节点,保存所有引用者的地址,当释放时设置所有地址为空。
问答 被weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable么?里面的结构可以画出来么?
对象被释放时执行 obj->rootDealloc()
,如果有弱引用标记,则会执行 objc_destructInstance
方法后释放。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 void *objc_destructInstance (id obj) { if (obj) { bool cxx = obj->hasCxxDtor(); bool assoc = obj->hasAssociatedObjects(); if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj); obj->clearDeallocating(); } return obj; } inline void objc_object::clearDeallocating() { if (slowpath(!isa.nonpointer)) { sidetable_clearDeallocating(); } else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) { clearDeallocating_slow(); } assert(!sidetable_present()); } void objc_object::sidetable_clearDeallocating() { SideTable& table = SideTables()[this ]; table.lock(); RefcountMap::iterator it = table.refcnts.find(this ); if (it != table.refcnts.end()) { if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) { weak_clear_no_lock(&table.weak_table, (id)this ); } table.refcnts.erase(it); } table.unlock(); }
可以看到在 sidetable_clearDeallocating
方法中,最后执行了 weak_clear_no_lock
清空了所有引用关系。
SideTable
表结构如下图:
总结 weak原理是绕不开的经典课题,通过阅读开源代码对苹果如何实现有了大致的了解,受益匪浅。
阅读过程中还惊叹于苹果各种花式小技巧,由于文章篇幅有限没来得及介绍,感兴趣可以了解一下,比如 DisguisedPtr
。
资料分享 Objective-C Class Ivar Layout 探索
理解 ARC 实现原理
weak 弱引用的实现方式