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

Objective-C高級編程 iOS與OS X多線程和內(nèi)存管理

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


Blocks

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

目錄

  1. Block的定義
  2. Block有哪幾種類型
  3. Block特性
  4. __block修飾符
  5. block調(diào)用copy方法的內(nèi)部實現(xiàn)
  6. block的循環(huán)引用問題
  7. 總結(jié)

1. block的定義

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

block的數(shù)據(jù)結(jié)構(gòu)

block也是對象, 以下對block結(jié)構(gòu)體的成員作簡單解釋

  • isa : 指向Class對象的指針, 所有對象都有該指針
  • flags : 用于按 bit 位表示一些 block 的附加信息
  • reserved : 保留變量
  • invoke : 指向函數(shù)的指針, 指向block的實現(xiàn)代碼
  • descriptor : 表示該 block 的附加描述信息宏悦,主要是 size 大小焰坪,以及 copy 和 dispose 函數(shù)的指針。
  • 捕捉到的變量 : 捕捉過來的變量拷恨,block 之所以能夠訪問它外部的局部變量舔亭,就是因為將這些變量(或?qū)ο蟮牡刂罚┛截惖搅私Y(jié)構(gòu)體中些膨。
  • copy : 用于保留捕獲的對象
  • dispose : 用于釋放捕獲的對象

2. block的類型

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

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

設(shè)置對象的存儲域
_NSConcreteStackBlock
_NSConcreteGlobalBlock 程序的數(shù)據(jù)區(qū)域(.data區(qū))
_NSConcreteMallocBlock

說明 :

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

全局塊(_NSConcreteGlobalBlock)

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

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

棧塊(_NSConcreteStackBlock)

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

從棧復(fù)制到堆上的Block與__block變量

堆塊(_NSConcreteMallocBlock)

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

ARC下的block類型

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

ARC下,  blk = block;  相當(dāng)于  blk = [block copy];

3. block特性

1. 截獲自動變量值

1> 對于 block 外的變量引用,block 默認是將其復(fù)制到其數(shù)據(jù)結(jié)構(gòu)中來實現(xiàn)訪問的. 也就是說block的自動變量截獲只針對block內(nèi)部使用的自動變量, 不使用則不截獲, 因為截獲的自動變量會存儲于block的結(jié)構(gòu)體內(nèi)部, 會導(dǎo)致block體積變大.

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

輸出為
age = 10

2> 對于用 __block 修飾的外部變量引用分歇,block 是復(fù)制其引用地址來實現(xiàn)訪問的.

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

輸出為
age = 18

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

2. 截獲對象

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

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

4. __block修飾符

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

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

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

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


5. copy

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

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

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

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

當(dāng)然, 當(dāng)block被銷毀時, block持有的__block也會被釋放

Block廢棄和__block變量的釋放

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

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

復(fù)制__block變量

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


什么時候我們需要手動對block調(diào)用copy方法

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

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

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

我們對不同地方的block調(diào)用copy會產(chǎn)生什么效果呢?

Block的類 副本源的配置存儲域 拷貝效果
_NSConcreteStackBlock 從椏兀拷貝到堆
_NSConcreteGlobalBlock 程序的數(shù)據(jù)區(qū)域 什么也不做
_NSConcreteMallocBlock 引用計數(shù)增加

所以, 不管block是什么類型, 在什么地方, 用copy方法都不會引起任何問題.如下表格所示. 就算是反復(fù)多次調(diào)用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持有
...下面不斷重復(fù)該過程

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

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

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


6. block的循環(huán)引用

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

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

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


7. Block總結(jié) :

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

推薦幾篇有關(guān)block的文章
談Objective-C block的實現(xiàn)
讓我們來深入簡出block吧


歡迎大家關(guān)注@Jerry4me, 關(guān)注菜鳥成長_. 我會不定時更新一些學(xué)習(xí)心得與文章.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市职抡,隨后出現(xiàn)的幾起案子葬燎,更是在濱河造成了極大的恐慌,老刑警劉巖缚甩,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谱净,死亡現(xiàn)場離奇詭異,居然都是意外死亡擅威,警方通過查閱死者的電腦和手機壕探,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來郊丛,“玉大人李请,你說我怎么就攤上這事±魇欤” “怎么了导盅?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長揍瑟。 經(jīng)常有香客問我白翻,道長,這世上最難降的妖魔是什么绢片? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任滤馍,我火速辦了婚禮岛琼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘巢株。我一直安慰自己槐瑞,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布纯续。 她就那樣靜靜地躺著随珠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪猬错。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天茸歧,我揣著相機與錄音倦炒,去河邊找鬼。 笑死软瞎,一個胖子當(dāng)著我的面吹牛逢唤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播涤浇,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼鳖藕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了只锭?” 一聲冷哼從身側(cè)響起著恩,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蜻展,沒想到半個月后喉誊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡纵顾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年伍茄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片施逾。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡敷矫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出汉额,到底是詐尸還是另有隱情曹仗,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布闷愤,位于F島的核電站整葡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏讥脐。R本人自食惡果不足惜遭居,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一啼器、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧俱萍,春花似錦端壳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至岳颇,卻和暖如春照捡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背话侧。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工栗精, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瞻鹏。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓悲立,卻偏偏與公主長得像,于是被迫代替她去往敵國和親新博。 傳聞我的和親對象是個殘疾皇子薪夕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容