Objective-C高級編程讀書筆記之blocks

Objective-C高級編程 iOS與OS X多線程和內存管理

Objective-C高級編程讀書筆記三部曲已經寫完, 另外兩篇如下 :
Objective-C高級編程讀書筆記之內存管理
Objective-C高級編程讀書筆記之GCD


Blocks

這里有五道關于block的測試題, 大家可以去做做測試看看自己對block了解多少.

目錄

  1. Block的定義
  2. Block有哪幾種類型
  3. Block特性
  4. __block修飾符
  5. block調用copy方法的內部實現
  6. block的循環引用問題
  7. 總結

1. block的定義

block是Objective-C對于閉包的實現(閉包是一個函數<或者指向函數的指針>加上函數有關的自由變量).

block的數據結構

block也是對象, 以下對block結構體的成員作簡單解釋

  • isa : 指向Class對象的指針, 所有對象都有該指針
  • flags : 用于按 bit 位表示一些 block 的附加信息
  • reserved : 保留變量
  • invoke : 指向函數的指針, 指向block的實現代碼
  • descriptor : 表示該 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函數的指針。
  • 捕捉到的變量 : 捕捉過來的變量,block 之所以能夠訪問它外部的局部變量,就是因為將這些變量(或對象的地址)拷貝到了結構體中。
  • copy : 用于保留捕獲的對象
  • dispose : 用于釋放捕獲的對象

2. block的類型

  • 全局塊(_NSConcreteGlobalBlock)
  • 棧塊(_NSConcreteStackBlock)
  • 堆塊(_NSConcreteMallocBlock)

這三種block各自的存儲域如下表

設置對象的存儲域
_NSConcreteStackBlock
_NSConcreteGlobalBlock 程序的數據區域(.data區)
_NSConcreteMallocBlock

說明 :

  • 全局塊存在于全局內存中, 相當于單例.
  • 棧塊存在于棧內存中, 超出其作用域則馬上被銷毀
  • 堆塊存在于堆內存中, 是一個帶引用計數的對象, 需要自行管理其內存

全局塊(_NSConcreteGlobalBlock)

  • block定義在全局變量的地方
  • block沒有截獲任何自動變量

以上兩個情況滿足任意一個則該block為全局塊, 全局塊的生命周期貫穿整個程序, 相當于單例.

棧塊(_NSConcreteStackBlock)

只要不是全局塊, 且block沒有被copy, 就是棧塊.棧塊的生命周期很短, 當前作用域結束, 該block就被廢棄. 要想在當前作用域以外的地方使用該block, 應該把該block從棧copy到堆上

從棧復制到堆上的Block與__block變量

堆塊(_NSConcreteMallocBlock)

簡單來說, 棧塊copy之后就變成堆塊, 這簡單吧~

ARC下的block類型

因為ARC下默認變量修飾符為__strong, 所以我們接觸到的block幾乎全是堆block和全局block.

ARC下,  blk = block;  相當于  blk = [block copy];

3. block特性

1. 截獲自動變量值

1> 對于 block 外的變量引用,block 默認是將其復制到其數據結構中來實現訪問的. 也就是說block的自動變量截獲只針對block內部使用的自動變量, 不使用則不截獲, 因為截獲的自動變量會存儲于block的結構體內部, 會導致block體積變大.

拷貝
int age = 10;
myBlock block = ^{
    NSLog(@"age = %d", age);
};
age = 18;
block();

輸出為
age = 10

2> 對于用 __block 修飾的外部變量引用,block 是復制其引用地址來實現訪問的.

引用地址
__block int age = 10;
myBlock block = ^{
    NSLog(@"age = %d", age);
};
age = 18;
block();

輸出為
age = 18

意味著對于第一種情況, 在block外部修改變量的值并不會應該block內部變量的值.而第二種情況則反之.
并且第一種情況block內部不允許修改變量的值, 第二種情況下可以. (有例外, 靜態變量, 靜態全局變量, 全局變量即使不使用__block修飾符也可以在block內部修改其值)

2. 截獲對象

對象不同于自動變量, 就算對象不加上__block修飾符, 在block內部能夠修改對象的屬性.
block截獲對象與截獲自動變量有所不同.
堆塊會持有對象, 而不會持有__block修飾的對象, 而棧塊永遠不會持有對象, 為什么呢?

  1. 堆塊作用域不同于棧塊, 堆塊可以超出其作用域地方使用, 所以堆塊結構體內部會保留對象的強指針, 保證堆塊在生命周期結束之前都能訪問對象. 而對于__block對象為什么不會持有呢? 原因很簡單, 因為block對象會跟隨block被復制到堆中, block再去引用堆中的對象(后面會講這個過程)..
  1. 棧塊只能在當前作用域下使用, 所以其內部不會持有對象. 因為不存在在作用域之外訪問對象的可能(棧離開當前作用域立馬被銷毀)

4. __block修飾符

為什么__block修飾符修飾的變量就能夠在block內部修改呢?? 原因在此
利用clang -rewrite-objc 源代碼文件名便可揭開其神秘的面紗.

__block int val = 10;
轉換成
__Block_byref_val_0 val = {
    0,
    &val,
    0,
    sizeof(__Block_byref_val_0),
    10
};

天哪! 一個局部變量加上__block修飾符后竟然跟block一樣變成了一個__Block_byref_val_0結構體類型的自動變量實例.

此時我們在block內部訪問val變量則需要通過一個叫__forwarding的成員變量來間接訪問val變量(下面會對__forwarding進行詳解)


5. copy

block的copy操作究竟做了什么呢?

這里不得不提及__block變量的存儲域

__block變量的配置存儲域 block從棧復制到堆時的影響
從棧復制到堆并被Block持有
被Block持有
Block中使用__block變量

由上圖可知, 對一個棧塊進行copy操作會連同block與__block變量(不管有沒有使用)在內一同copy到堆上, 并且block會持有__block變量(使用).
ps : 堆上的block及__block變量均為對象, 都有各自的引用計數

當然, 當block被銷毀時, block持有的__block也會被釋放

Block廢棄和__block變量的釋放

到這里我們能知道, 此思考方式與Objective-C的引用計數內存管理完全相同.

那么有人就會問了, 既然__block變量也被復制到堆上去了, 那么訪問該變量是訪問棧上的還是堆上的呢?? __forwarding 終于要閃亮登場了

復制__block變量

通過__forwarding, 無論實在block中, block外訪問__block變量, 也不管該變量在棧上或堆上, 都能順利地訪問同一個__block變量.


什么時候我們需要手動對block調用copy方法

前面我們說到 : 要想在當前作用域以外的地方使用該block, 應該把該block從棧copy到堆上. 實際上, 在ARC下, 以下幾種情況下, 編譯器會幫我們把棧上的block復制到堆中

  • block作為函數返回值返回時
  • 將block賦值給__strong修飾符id類型或block類型成員變量時
  • 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中傳遞block時

理論上我們只有把block作為函數/方法的參數傳入時才需要對block進行copy操作.

我們對不同地方的block調用copy會產生什么效果呢?

Block的類 副本源的配置存儲域 拷貝效果
_NSConcreteStackBlock 從棧拷貝到堆
_NSConcreteGlobalBlock 程序的數據區域 什么也不做
_NSConcreteMallocBlock 引用計數增加

所以, 不管block是什么類型, 在什么地方, 用copy方法都不會引起任何問題.如下表格所示. 就算是反復多次調用copy方法, 如

blk = [[[[blk copy] copy] copy] copy];

該源碼可解釋如下 :

{
    block tmp = [blk copy]; // block被tmp持有
    blk = tmp; // block被tmp和blk持有
} 
// tmp超出作用域, 其指向的block也被釋放, block被blk持有
{
    block tmp = [blk copy]; // block被tmp和blk持有
    blk = tmp; // blk指向的舊block釋放, 并強引用新block, 最終block被tmp和blk持有
}
// tmp超出作用域, 其指向的block也被釋放, block被blk持有
...下面不斷重復該過程

我們知道, 這只是一個循環的過程, block被tmp持有 -> block被tmp和blk持有 -> block被blk持有 -> block被tmp和blk持有 -> ......

由此可得知, 在ARC下該代碼也沒有任何問題.

總結 : 如果block需要給作用域外的地方使用, 但是你不知道需不需要copy, 那就copy吧. 反正不會錯


6. block的循環引用

這部分相信大家都清楚怎樣做能破環, 所以我在這就只簡單說兩句

  • MRC下用__block可以避免循環引用(原因見上面block特性之截獲自動變量值)
  • ARC下用__weak來避免循環引用

這里需要提醒大家的是, 只有堆塊(_NSConcreteMallocBlock)才可能會造成循環引用, 其他兩種block不會


7. Block總結 :

  • block相比函數更加方便, 高效, 蘋果強烈推薦使用
  • ARC下編譯器會幫助我們更好地管理block的生命周期
  • ARC下block屬性聲明為strong或copy其實都一樣, 因為編譯器內部會幫我們實現copy方法
  • 善用__weak(ARC)或__block(MRC)來避免循環引用

推薦幾篇有關block的文章
談Objective-C block的實現
讓我們來深入簡出block吧


歡迎大家關注@Jerry4me, 關注菜鳥成長_. 我會不定時更新一些學習心得與文章.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容