以前的文章有說過 Atomic
原子操做的原理,其做爲一個特殊的修飾前綴,影響了存取操做。c++
在屬性修飾定義中,還有另外一類修飾前綴,他們分別是 strong
weak
assign
copy
,這些又有什麼區別呢?macos
平時喜歡探究的同窗,可能也見過 unsafe_unretained
,這個又是什麼呢?數組
讓咱們從屬性修飾入手,逐步揭開弱引用的面紗。bash
首先咱們先建立一個示例代碼文件做爲樣本。markdown
#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屬性不能翻譯)數據結構
展開的代碼比較多,我這裏截取關鍵部分探討。app
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
修飾。ide
下面咱們來看一下方法具體實現。函數
// @implementation PropertyObject //根據偏移取值和賦值 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); //只有Copy不一樣,setter的實現是objc_setProperty 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; } // @end 複製代碼
在代碼中,只有copy
修飾屬性的setter
方法使用了objc_setProperty
,其餘幾種都是根據 self + 偏移量
的方式計算出內存地址直接進行存取。oop
那問題來了,若是真的是那麼簡單的話,arc
是怎麼實現根據不一樣修飾從而進行內存管理的呢?
原來經過 clang -rewrite-objc
的代碼只是翻譯成 c++
語言,在以後的編譯過程當中會進一步處理。
接着使用 clang -S -fobjc-arc -emit-llvm main.m -o main.ll
命令生成中間碼。
(中間碼顯示比較雜亂,我根據本身理解整理成簡潔版)
//代碼整理後 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 |
本文篇幅有限,暫不介紹 storeStrong
和 objc_setProperty_nonatomic_copy
,主要介紹 weak
相關操做。
打開 objc4-750
開源代碼,翻到 NSObject.mm
,咱們來一探究竟。
// 初始化弱引用 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
中調用的都是一個方法,區別在於使用了不一樣的模版,那麼咱們來看下對一個地址的存取方法。
// 獲取操做的具體實現 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()) { // 若是使用的是系統默認的內存管理,則保證了已經初始化 // 因此能夠直接rootTryRetain assert(cls->isInitialized()); if (! obj->rootTryRetain()) { result = nil; } } else { // 若是不是默認的,則須要確保在初始化線程上執行自定義retain操做 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
。
//註銷引用 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. // 上面爲蘋果註釋,看這意思應該是objc_storeWeak還須要使用引用地址作後續處理。 } //註冊引用 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; //引用人 // taggedPointer沒有引用計數,不須要處理 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); } // Do not set *referrer. objc_storeWeak() requires that the // value not change. 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) { //這裏應該確定有entry,由於調用前判斷了對象的WeaklyReferenced //若是確實沒有,蘋果認爲多是CF/objc緣由 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
的變量進行存取操做,其實都是經過被操做的對象地址查找到相應的表,而後增刪表的引用數組內容。
關鍵就在於怎麼申明建立表,以及這個表是怎麼設計及使用的。
// SideTables 類型申明 // 這裏之因此先使用數據的方式申明是由於考慮到加載順序的問題 alignas(StripedMap<SideTable>) static uint8_t SideTableBuf[sizeof(StripedMap<SideTable>)]; // 加載image時執行初始化 static void SideTableInit() { new (SideTableBuf) StripedMap<SideTable>(); } // 數組還原成StripedMap類型 static StripedMap<SideTable>& SideTables() { return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf); } // StripedMap 的結構 enum { CacheLineSize = 64 }; template<typename T> class StripedMap { #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR enum { StripeCount = 8 }; #else enum { StripeCount = 64 }; #endif // 64位對齊 struct PaddedT { T value alignas(CacheLineSize); }; // 手機系統數組個數爲8 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
類的內部結構。
// 哈希散列表,使用補碼的形式把指針地址做爲Key,保存引用計數 typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap; // Template parameters. 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; //計算輔助量,數值爲數組總數-1 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 { // 當數量超過4個時,結構轉爲指針,每次容量滿的時候就擴容兩倍 // 須要與數組做區分,因此有out_of_line_ness標記 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
中有一個聯合體,這又是怎麼操做實現的呢?
// 添加新引用者 static void append_referrer (weak_entry_t *entry, objc_object **new_referrer) { // 沒有超過4個,就用內斂數組 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; } } // 若是超過4個了,就從數組結構轉成指針結構 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; //最大hash移位次數,用於優化循環 // 因爲只有4個,會在下個判斷後執行grow_refs_and_insert初始化並插入新對象 } // 斷言必然是指針結構 assert(entry->out_of_line()); // 若是指針數量超過3/4,就容量翻倍後再插入 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; //下標+1後取餘 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
方法後釋放。
void *objc_destructInstance(id obj) { if (obj) { // Read all of the flags at once for performance. bool cxx = obj->hasCxxDtor(); bool assoc = obj->hasAssociatedObjects(); // This order is important. if (cxx) object_cxxDestruct(obj); //調用析構函數 if (assoc) _object_remove_assocations(obj); //移除關聯對象關係 obj->clearDeallocating(); //處理isa } return obj; } inline void objc_object::clearDeallocating() { if (slowpath(!isa.nonpointer)) { // Slow path for raw pointer isa. sidetable_clearDeallocating(); } else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) { // Slow path for non-pointer isa with weak refs and/or side table data. 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
。