讓我們來深入淺出block吧

phpv5751 8年前發布 | 9K 次閱讀 iOS開發 移動開發

開始之前,我想先提幾個問題,看看大家是否對此有疑惑。唐巧已經寫過一篇對block很有研究的 文章 ,大家可以去看看(本文會部分引用巧哥文中出現的圖和代碼)。在巧哥的基礎上,我補充一些block相關的知識點和代碼,并且概括并修正一些觀點。

1.block是什么?block是對象嗎?

2.block分為哪幾種?__blcok關鍵字的作用?

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

4.block的生命周期?

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

block是什么?block是對象嗎?

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

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 語言對于閉包的實現。

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

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

回到上文,為什么說block是對象呢,原因就在于isa指針。那么這個isa指針是何物呢?

所有對象的都有isa 指針,用于實現對象相關的功能。

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

 

block的數據結構

block分為哪幾種?__blcok關鍵字的作用?

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

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

簡單地講,如果一個block鐘沒有引用外部變量并且沒有被其他對象持有,就是NSConcreteGlobalBlock。

如下圖所示:

NSConcreteGlobalBlock

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

什么是NSConcreteStackBlock呢:

可以這么理解,NSConcreteStackBlock就是引用了外部變量的block,上代碼:

NSConcreteStackBlock

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

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

證據如下:

 

NSConcreteStackBlock不會持有外部對象

從打印的日志可以看出,引用計數始終沒變。

NSConcreteMallocBlock:

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

NSConcreteMallocBlock

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

NSConcreteMallocBlock會持有外部對象

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

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

先上一個例子,你們很快就會明白了

__block example1

沒錯,前文說過,block引用外部是以捕獲的形式來捕捉的,而沒有聲明__block,則會將外部變量copy進block,若用了__block,則是復制其引用地址來實現訪問。這就是為什么聲明了__block,在block內部改變就會對外有影響的原因了。

注意!!這里需要知道的是,在MRC環境下,如果沒有用__block,會對外部對象采用copy的操作,而用了__block則不會用copy的操作。

上代碼:

__block example2

哈哈哈,怎么樣,所以從更底層的角度來說, 在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)來解決:

weak解決循環引用

看,現在就可以愉快的釋放了。

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

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

其實block與函數和方法一樣,對于傳進來的參數,并不會持有

證據如下:

block不會持有參數對象

總結:

到這里,對于block的介紹結束了。實際運用中其實不用太關心這些原理的,只需要正確掌握好block的生命周期就可以靈活地運用block了。但是對于一個資深開發者來說,block的深層次掌握還是必須的!

好的,下次再見!

歡迎大家關注我的微博,我會經常在上面分享一些大家感興趣的東西。

來自: http://www.jianshu.com/p/e03292674e60

 本文由用戶 phpv5751 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!