讓咱們來深刻淺出block吧

開始以前,我想先提幾個問題,看看你們是否對此有疑惑。唐巧已經寫過一篇對block頗有研究的文章,你們能夠去看看(本文會部分引用巧哥文中出現的圖和代碼)。在巧哥的基礎上,我補充一些block相關的知識點和代碼,而且歸納並修正一些觀點。安全

1.block是什麼?block是對象嗎?數據結構

2.block分爲哪幾種?__blcok關鍵字的做用?閉包

3.block在ARC和MRC下的區別?ide

4.block的生命週期?函數

5.block對於以參數形式傳進來的對象,會不會強引用??翻譯


block是什麼?block是對象嗎?

先介紹一下什麼是閉包。在 wikipedia 上,閉包的定義) 是:3d

In programming languages, a closure is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.指針

翻譯過來,閉包是一個函數(或指向函數的指針),再加上該函數執行的外部的上下文變量(有時候也稱做自由變量)。日誌

block 實際上就是 Objective-C 語言對於閉包的實現。cdn

block是否是對象?答案顯而易見:是的。

下圖是block的數據結構定義,顯而易見,在Block_layout裏,咱們看到了isa指針,這裏咱們不具體對isa指針展開,也不對block具體數據結構展開,想了解詳細能夠看唐巧的文章。

回到上文,爲何說block是對象呢,緣由就在於isa指針。那麼這個isa指針是何物呢?

全部對象的都有isa 指針,用於實現對象相關的功能。

看到這,你應該明白,block其實就是objc對於閉包的對象實現。


block分爲哪幾種?__blcok關鍵字的做用?

分爲三種,即NSConcreteGlobalBlock、NSConcreteStackBlock、NSConcreteMallocBlock。

詳細剖析這三種block,首先是NSConcreteGlobalBlock:

簡單地講,若是一個block鐘沒有引用外部變量 而且沒有被其餘對象持有 ,就是NSConcreteGlobalBlock。

以下圖所示:

須要注意的是,NSConcreteGlobalBlock是全局的block,在編譯期間就已經決定了,如同宏同樣。

什麼是NSConcreteStackBlock呢:

能夠這麼理解,NSConcreteStackBlock就是引用了外部變量的block,上代碼:

OK,咱們已經知道了NSConcreteStackBlock,那麼它和NSConcreteGlobalBlock有什麼區別呢?難道僅僅是引用了外部變量與否的區別嗎?答案是否認的。

其實NSConcreteStackBlock內部會有一個結構體__main_block_impl_0,這個結構體會保存外部變量,使其體積變大。而這就致使了NSConcreteStackBlock並不像宏同樣,而是一個動態的對象。而它因爲沒有被持有,因此在它的內部,它也不會持有其外部引用的對象。

證據以下:

從打印的日誌能夠看出,引用計數始終沒變。

NSConcreteMallocBlock:

看似最爲神祕的NSConcreteMallocBlock其實就是一個block被copy時,將生成NSConcreteMallocBlock(block沒有retain)。怎麼樣,是否是很簡單0 0

須要注意的是,NSConcreteMallocBlock會持有外部對象!

看到了吧,只要這個NSConcreteMallocBlock存在,內部對象的引用計數就會+1。

下面來講說__block這個關鍵字:

先上一個例子,大家很快就會明白了

沒錯,前文說過,block引用外部是以捕獲的形式來捕捉的,而沒有聲明block,則會將外部變量copy進block,若用了block,則是複製其引用地址來實現訪問。這就是爲何聲明瞭__block,在block內部改變就會對外有影響的緣由了。

注意!!這裏須要知道的是,在MRC環境下,若是沒有用block,會對外部對象採用copy的操做,而用了block則不會用copy的操做。

上代碼:

哈哈哈,怎麼樣,因此從更底層的角度來講,在MRC環境下,__block根本不會對指針所指向的對象執行copy操做,而只是把指針進行的複製。而這一點每每是不少新手&老手所不知道的!

而在ARC環境下,對於聲明爲__block的外部對象,在block內部會進行retain,以致於在block環境內能安全的引用外部對象,因此要謹防循環引用的問題!


block在ARC和MRC下的區別?

首先要指正下巧哥博客的觀點:

在 ARC 開啓的狀況下,將只會有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 類型的 block。

在上面介紹NSConcreteStackBlock的時候,是在ARC環境下跑的,而打印出來的日誌明確的顯示出,當時的block類型爲NSConcreteStackBlock。

而實際上,爲何你們廣泛會認爲ARC下不存在NSConcreteStackBlock呢?

這是由於自己咱們經常將block賦值給變量,而ARC下默認的賦值操做是strong的,到了block身上天然就成了copy,因此經常打印出來的block就是NSConcreteMallocBlock了。

so,在ARC下,大部分的應用場景下,幾乎能夠說是所有都爲NSConcreteMallocBlock或者是NSConcreteGlobalBlock。那麼問題來了,咱們知道NSConcreteMallocBlock是會持有外部變量的,而此時若是它所持有的外部變量正好又持有它,就會產生循環引用的問題。

讓咱們來聊聊block的生命週期!


block的生命週期?

談到block生命週期,其實這是一個很是嚴肅的話題,雖然block簡單易用,老小皆宜,可是一旦使用不慎容易形成「強擼灰飛煙滅」的後果(內存泄露)。

ps:接下來的例子都用ARC來展現了

首先展現:

不用看了,這個object永遠也不會被釋放,這是一個很典型的循環引用情形。object持有了block(讀者能夠想象此處爲什麼爲NSConcreteMallocBlock,提示:在ARC環境下),而block又持有了object,因而形成死鎖,object不再會被釋放了。此時機智的編譯器給了你warning,可是在不少複雜的狀況下,編譯器並不能識別出循環引用的場景。而此時你就須要注意了!

那麼,我是如何來處理block的生命週期相關問題的呢,首先前文提到,block是一個對象,既然是一個對象,它必然有着和對象同樣的生命週期即若是沒有被引用就會被釋放。

因此block的生命週期歸結起來很簡單,只要看持有block的對象是否是也被block持有,若是沒有持有,就不用擔憂循環引用問題了。

可是像上面的狀況,若是產生相互持有的狀況該腫麼辦!

你能夠用weak(ARC)或block(MRC)來解決:

看,如今就能夠愉快的釋放了。


block對於以參數形式傳進來的對象,會不會強引用?

唉,不知不覺已經快半夜2點了,對於這部分的話,其實也是閒着蛋疼在想這個問題。

其實block與函數和方法同樣,對於傳進來的參數,並不會持有

證據以下:


總結:

到這裏,對於block的介紹結束了。實際運用中其實不用太關心這些原理的,只須要正確掌握好block的生命週期就能夠靈活地運用block了。可是對於一個資深開發者來講,block的深層次掌握仍是必須的!

好的,下次再見!

歡迎你們關注@kuailejim,我會常常在上面分享一些你們感興趣的東西。

相關文章
相關標籤/搜索