在往深處看-block(一)中咱們已經探究過在MRC環境下Block的三種類型以及其關係。api
咱們發現有些狀況下ARC和MRC環境下的block是不一樣的,這是由於ARC幫咱們作了一些咱們在OC層面看不見的事情。好比ARC下某些狀況編譯器會自動對block進行一次copy操做,將本來存在棧上的block拷貝到堆空間上。安全
上圖中執行的環境是ARC,咱們能夠看出本是一個__NSStackBlock__
,當它做爲一個函數的返回值的時候它變成__NSMallocBlock__
類型,上篇中已經指出,__NSStackBlock__
通過copy操做就會變成__NSMallocBlock__
類型,而這裏並無顯式的調用copy,這即是ARC爲咱們作的。iphone
block做爲強指針引用的時候也會自動調用copy,將棧區的block拷貝一份到堆區。函數
當block做爲Cocoa API中方法名含有usingBlock的方法參數和block做爲GCD API的方法參數時ARC都會自動爲block執行一次copy操做,好比:3d
因此,爲了保證block的釋放正和時宜,而不是像在棧空間上的變量似的隨時可能被釋放。在MRC的時候聲明一個block的時候一般使用copy關鍵字修飾,而到了ARC雖然不用咱們顯式的執行copy,爲了提醒咱們block是經過copy操做存在於堆空間的咱們仍是會用copy關鍵修飾,這只是一個習慣,其實用strong也是能夠的。指針
在往深處看-block(一)中簡述了block對基本數據變量的捕獲,那麼對於auto對象block是怎麼捕獲的呢?會不會對auto對象產生相應的引用呢?code
在MRC下聲明一個屬性Person,並在Block中訪問其屬性age,觀察Person何時被釋放:cdn
咱們看到Person在block的大括號結束以後,尚未調用block0的時候Person對象就已經釋放了,說明在棧上的block並無對Person有強引用。對象
對block0的值執行一次copy操做,讓block0持有該block,而後執行上述代碼,發如今block沒有release以前,person對象沒有被釋放,因此堆上的block強引用了person對象,person執行一次release以後,person的引用計數依然沒有成爲0,由於,block還引用着它。這是由於當對block進行copy操做的時候,block會執行內部的__main_block_copy_0
方法。__main_block_copy_0
方法執行_Block_object_assign
根據變量的修飾符判斷對捕獲的對象的引用狀況(retain或者弱引用)。
而當block從堆中移除的時候,會調用與__main_block_copy_0
對應的_Block_object_dispose
函數,該函數會自動釋放引用的auto變量。
關注上述源碼中框出的部分,對比基本數據類型的變量捕獲,咱們發如今Desc結構體多了上面所說的copy和dispose函數指針,它們的做用和調用時機也在上面描述了。
因爲MRC狀況下並無弱引用也就沒有__weak這個關鍵字,因此在ARC下探究block對對象的引用狀況。
依舊是上述代碼,將他改爲ARC環境:去掉block的copy,對象的release,以及Person的dealloc方法中對父類dealloc方法的調用。
而對對象的強弱引用是在runtime的時候進行斷定的。因此在將.m轉成.cpp的時候須要聲明一下runtime環境,使用該命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
上面圖片中 block經過_Block_object_assign
判斷修飾person的關鍵字爲__strong,對person進行強引用。
而使用__weak修飾person的時候會產生弱引用:
當咱們在block內部嘗試修改auto變量的時候會發現出現一個錯誤:Variable is not assignable (missing __block type specifier)
編譯器提示咱們使用__block
修飾auto成員變量,才能進行修改。
利用上面提到的命令將代碼轉換成.cpp查看實現,發現出現了一個新的包含有isa指針和auto變量同名成員的結構體。它的每一個成員的值以下圖:
當咱們修改a這個值得時候會經過結構體找到__forwarding
這個指針,經過賦值咱們發現這個指針指向其自身,而後在經過__forwarding
指向的結構體查找到a這個成員,改掉a這個成員的值。那麼auto變量a與生成的對象a以及對象的成員a之間是什麼關係呢?
咱們嘗試模擬block底層的轉換窺探其關係:
獲取地址值:
__forwarding
指向的是結構體自己,而結構體內存儲的a的地址跟auto變量a的地址是一致的。block內部存儲的爲a從新生成的對象a與auto變量自己是不一樣的。
既然block爲auto變量a生成了一個新的對象,那麼這個對象在內存中是如何管理的呢?
它跟對象類型的變量同樣,當block存在於棧上的時候都不會對他們產生強引用,而當block從棧上copy到堆上的時候,都會調用__main_block_copy_0
,經過_Block_object_assign
函數判斷引用關係,而該函數的最後一個參數代表了是被__block修飾auto變量仍是對象類型的auto變量。
當block從堆上移除的時候都會調用__main_block_dispose_0
函數,並傳入上述數字所表明的含義。
上述代碼person引用了block,而block內部又對person進行了引用,因此此時person的引用計數爲2。當person的做用域結束的時候,person的引用計數減1,而block並無被執行,因此person的引用計數依然是1,不能被回收。爲了使person的內存可以正確的被回收,這個循環中必須有一個引用是弱引用才能保證對象被安全的釋放。咱們一般可使用__weak
或者__unsaft_unretained
修飾被訪問的對象。這兩個關鍵字的含義都是聲明一個不會被強引用的對象,因此block對person的引用是弱引用,因此當person的被release一次以後,就沒有其餘的引用了,person的引用計數就會爲0,系統就會回收person的內存,完成內存釋放。
除了上述的兩種方法,還能夠在block使用變量以後對變量進行置nil操做。可是這中作法必須對block進行一次調用,不然,block永遠都不會執行置nil的操做,block對person的引用也會一直存在。因爲在block內部對person進行了修改,因此使用__block
關鍵字對person進行修飾。而使用__block
修飾以後,block又會在內部生成一個person對象,這樣又會多了一次引用,與上面的狀況不一樣。
在block內部將person置爲nil就是打破了__block引用person這條線,而後引用計數person的引用計數減1,知道person的引用計數爲0的時候block釋放,關於person的內存管理就完成了。
這種作法中重要的一點是必須調用一次block,才能使block中引用person被釋放。
1.block是一個封裝了函數調用和函數調用環境的OC對象(由於其結構體的第一個成員是isa指針)。
2.block對auto變量的捕獲是值傳遞,static變量是指針傳遞,全局變量不會進行捕獲。
3.block有三種類型__NSGlobalBlock__
__NSStackBlock__
__NSMallocBlock__
訪問了外部變量的block存在於棧上,通過一次copy操做會將棧上的block拷貝到堆上,即:__NSStackBlock__
變爲__NSMallocBlock__
,__NSGlobalBlock__
copy什麼也不作,__NSMallocBlock__
引用計數加1。
4.mrc下不會自動進行copy,arc環境下在block做爲返回值,被強指針引用,在cocoa api中做爲usingblock的參數,做爲gcd api的參數的時候回自動進行一次copy操做。
5.棧上block訪問了對象類型的auto變量的時候不會對其發生強引用。
6.block從棧上copy到堆上的時候,block內部會執行copy操做,_Block_object_assign
函數回經過auto變量的修飾符判斷髮生強弱引用。
7.block從堆中移除的時候,block內部會執行dispose,將引用的對象進行釋放。
8.__block
修飾的值,會在block內部生成一個對象,對象中存放了__block
的地址,當修改這個值得時候block內部會找到這個對象(結構體,由於其第一個成員是isa)的__forwarding(指向其自身)
而後在找到這個值得地址,直接修改地址中的存放值。
9.__block
生成的對象的強弱引用也是經過_Block_object_assign
函數對__block
產生的變量產生強引用。從堆中移除的時候調用dispose
釋放對__block
生成的變量的引用。
10.使用__weak
修飾在block中訪問變量解決循環引用的問題。