原做於:2018-01-08
GitHub Repo:BoyangBloghtml
在不特殊說明是MRC的狀況下,默認是ARC。 Objective-C Automatic Reference Counting (ARC)c++
咱們知道,在ARC中,除了全局block,block都是在棧上進行建立的。使用的時候,會自動將它複製到堆中。中間會經歷objc_retainBlock
-> _Block_copy
-> _Block_copy_internal
方法鏈。換過來講,咱們使用的每一個攔截了自動變量的block,都會經歷這寫方法(注意這一點很重要)。git
經過以前的研究,瞭解到在 __main_block_impl_0中會保存着引用到的變量。在轉換過的block代碼中,block會強行持有攔截的外部對象,無論有沒有改變過,都是會形成強引用。github
爲了作好準備,咱們先看一下**__strong和__weak**的實現過程。macos
__strong其實是一個默認的方法。數組
{
id __strong obj = [[NSObject alloc] init];
}
複製代碼
代碼會被轉換成這個樣子markdown
id __attribute__((objc_ownership(strong))) obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")); //代碼實際上只有一行,爲了方便觀看打了換行 複製代碼
抽離出來,實際上主要是這三個方法app
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj,selector(init));
objc_release(obj);
複製代碼
也就是說,ARC下的對象,正常狀況下都是__strong修飾的。ide
這裏咱們要使用 clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations main.m方法去轉換爲C++代碼,緣由是由於,__weak其實只在ARC的狀態下才能使用,以前使用 clang -rewrite-objc main.m是直接將代碼轉換爲C++,並不有限制。函數
聲明一個__weak對象
{
id __weak obj = strongObj;
}
複製代碼
轉換以後
id __attribute__((objc_ownership(none))) obj1 = strongObj;
複製代碼
相應的會調用
id obj ;
objc_initWeak(&obj,strongObj);
objc_destoryWeak(&obj);
複製代碼
從名字上能夠看出來,一個是建立一個是銷燬。
這裏LLVM文檔和objc_723文檔有些許不一樣。我這裏採用最新的objc_723代碼,比以前的有優化:
id objc_initWeak(id *location, id newObj) { // 查看對象實例是否有效 // 無效對象直接致使指針釋放 if (!newObj) { *location = nil; return nil; } // 這裏傳遞了三個 bool 數值 // 使用 template 進行常量參數傳遞是爲了優化性能 // DontHaveOld--沒有舊對象, // DoHaveNew--有新對象, // DoCrashIfDeallocating-- 若是newObj已經被釋放了就Crash提示 return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj); } ~~~~~~~~~~~~~~~~ void objc_destroyWeak(id *location) { (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating> (location, nil); } 複製代碼
這兩個方法,最後都指向了 storeWeak方法,這是一個很長的方法:
// Update a weak variable. // If HaveOld is true, the variable has an existing value // that needs to be cleaned up. This value might be nil. // If HaveNew is true, there is a new value that needs to be // assigned into the variable. This value might be nil. // If CrashIfDeallocating is true, the process is halted if newObj is // deallocating or newObj's class does not support weak references. // If CrashIfDeallocating is false, nil is stored instead. // 更新weak變量. // 當設置HaveOld是true,即DoHaveOld,表示這個weak變量已經有值,須要被清理,這個值也有能是nil // 當設置HaveNew是true, 即DoHaveNew,表示有一個新值被賦值給weak變量,這個值也有能是nil //當設置參數CrashIfDeallocating是true,即DoCrashIfDeallocating,若是newObj已經被釋放或者newObj是一個不支持弱引用的類,則暫停進程 // deallocating或newObj的類不支持弱引用 // 當設置參數CrashIfDeallocating是false,即DontCrashIfDeallocating,則存儲nil enum CrashIfDeallocating { DontCrashIfDeallocating = false, DoCrashIfDeallocating = true }; template <HaveOld haveOld, HaveNew haveNew, CrashIfDeallocating crashIfDeallocating> static id storeWeak(id *location, objc_object *newObj) { assert(haveOld || haveNew); // 初始化當前正在 +initialize 的類對象爲nil if (!haveNew) assert(newObj == nil); Class previouslyInitializedClass = nil; id oldObj; // 聲明新舊SideTable, SideTable *oldTable; SideTable *newTable; // 得到新值和舊值的鎖存位置(用地址做爲惟一標示) // 經過地址來創建索引標誌,防止桶重複 // 下面指向的操做會改變舊值 retry: // 若是weak ptr以前弱引用過一個obj,則將這個obj所對應的SideTable取出,賦值給oldTable 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. //經過確保沒有弱引用的對象具備未初始化的 isa,防止弱引用機制和 +initialize 機制之間的死鎖。 //在使用 +initialized 方法的時候,由於這個方法是在alloc以前調用的。不這麼作,可能會出現+initialize 中調用了 storeWeak 方法,而在 storeWeak 方法中 weak_register_no_lock 方法中用到對象的 isa 尚未初始化完成的狀況。 if (haveNew && newObj) { // 得到新對象的 isa 指針 Class cls = newObj->getIsa(); // 判斷 isa 非空且已經初始化 if (cls != previouslyInitializedClass && !((objc_class *)cls)->isInitialized()) { // 解鎖新舊SideTable SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); _class_initialize(_class_getNonMetaClass(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. // 若是 newObj 已經完成執行完 +initialize 是最理想狀況 // 若是 newObj的 +initialize 仍然在線程中執行 // (也就是說newObj的 +initialize 正在調用 storeWeak 方法) // 經過設置previousInitializedClass以在重試時識別它。 previouslyInitializedClass = cls; goto retry; } } // Clean up old value, if any. // 清除舊值,其實是清除舊對象weak_table中的location if (haveOld) { weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); } // Assign new value, if any. // 分配新值,其實是保存location到新對象的weak_table種 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. // 若是弱引用被釋放 weak_register_no_lock 方法返回 nil // 若是新對象存在,而且沒有使用TaggedPointer技術,在引用計數表中設置若引用標記位 if (newObj && !newObj->isTaggedPointer()) { // 標記新對象有weak引用,isa.weakly_referenced = true; newObj->setWeaklyReferenced_nolock(); } // Do not set *location anywhere else. That would introduce a race. // 設置location指針指向newObj // 不要在其餘地方設置 *location。 那會引發競爭 *location = (id)newObj; } else { // No new value. The storage is not changed. } SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); return (id)newObj; } 複製代碼
這裏再也不重複一遍weak的實現,有興趣的能夠去@property的研究(二)查看。
簡單點說,因爲weak也是用哈希表實現的,因此objc_storeWeak
函數就把第一個入參的變量地址註冊到weak表中,而後根據第二個入參來決定是否移除。若是第二個參數爲0,那麼就把**__weak**變量從weak
表中刪除記錄,並從引用計數表中刪除對應的鍵值記錄。
因此若是**__weak引用的原對象若是被釋放了,那麼對應的__weak**對象就會被指爲nil。這部分就是經過objc_storeWeak
函數這些函數來實現的。
__weak __typeof(self)weakSelf = self;
__strong __typeof(weakSelf)strongSelf = weakSelf;
複製代碼
weakSelf是爲了讓block不去持有self,避免了循環引用,若是在Block內須要訪問使用self的方法、變量,建議使用weakSelf。
可是,這裏會出現一個問題。使用weakSelf修飾的 **self.**變量,是有可能在執行的過程當中就被釋放的。
如下代碼爲例
- (void)blockRetainCycle_1 { __weak __typeof(self)weakSelf = self; self.block = ^{ NSLog(@"%@",@[weakSelf]); }; } 複製代碼
咱們若是直接使用這個函數,是有可能在打印以前,weakSelf就被釋放了,打印出來就是會出問題。 爲了解決這個問題,咱們就要用到strongSelf。
- (void)blockRetainCycle_2 { __weak __typeof(self)weakSelf = self; self.block = ^{ __strong typeof (weakSelf)strongSelf = weakSelf; NSLog(@"%@",@[strongSelf]); }; } 複製代碼
在這裏,咱們使用了strongSelf,它能夠保證在strongSelf下面,直到出了做用域以前,都是存在這個strongSelf的。
可是,這裏依然存在一個微小的問題:
咱們知道使用weakSelf的時候是沒法保證在做用域中一直持有的。雖然使用了strongSelf,可是仍是會存在微小的機率,讓weakSelf在strongSelf建立以前被釋放。若是是單純的給self對象發送信息的話,這麼其實問題不大,OC的消息轉發機制保證了咱們即便給nil的對象發送消息也不會出現問題。
可是若是咱們有其餘的操做,好比說將self對象添加進數組中,如上面代碼所示,這裏就會發生crash了。
那麼咱們要須要進一步的保護
- (void)blockRetainCycle_3 { __weak __typeof(self)weakSelf = self; self.block = ^{ __strong typeof (weakSelf)strongSelf = weakSelf; if (strongSelf) { NSLog(@"%@",@[strongSelf]); } }; } 複製代碼
咱們使用__block
其實也是可能達到防止block循環引用的。
咱們能夠經過在block內部把__block
修飾的對象置爲nil來變相地實現內存釋放。
從內存上來說,__block
會持有該對象,即便超出了該對象的做用域,該對象仍是會存在的,直到block對象從堆上銷燬;而__weak
是把該對象賦值給weak對象,若是對象被銷燬,weak對象將變成nil。
另外,__block
對象可讓block修改局部變量,__weak
則不能夠。
咱們經過以前的文章知道,在ARC當中,通常的block會從棧被copy到堆中。
可是若是使用weak呢?(assign就不討論了)
系統會告知咱們 Assigning block literal to a weak property; object will be released after assignment。
而在ARC下要使用什麼關鍵字呢?strong和copy都是能夠的。 經過以前的文章能夠知道,在ARC中,block會自動從棧被複制到堆中,這個copy是系統自動進行了,即便使用strong仍是依然會有copy操做。因此說,若是爲了嚴謹些,使用copy是能夠的,可是使用strong也無傷大雅。
下一篇文章block(四):修改block的實現