Objective-C Block 筆記二-Blocks的實現

Block 的實質

Block 是“帶有自動變量值的匿名函數”,我們可以通過 Block 的實現來加深理解捕仔。首先通過 clang 工具可以將含有 Block 的源代碼轉換為一般C語言代碼匕积。
將下面代碼保存為 test.m:

int main()
{
    void (^blk)(void) = ^{int i=0;};
    
    blk();
    
    return 0;
}

在終端執(zhí)行命令clang -rewrite-objc test.m,生成 test.cpp C 代碼文件榜跌,我們來看下 block 語句^{int i=0;};轉換后的代碼:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int i=0; }

__main_block_impl_0 是一個結構體闪唆,如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

__main_block_impl_0有兩個成員變量:impl,Desc钓葫,還有一個構造函數__main_block_impl_0(void*, struct, int)悄蕾。

我們先看impl__block_impl 類型础浮, block 結構體:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

第二個成員變量 Desc帆调,是__main_block_desc_0類型指針:

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} 

reserved 為預留字段,Block_size 是該 block 的大小

下面我們看看該結構體的構造函數

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
}

以上就是初始化 __main_block_impl_0結構體成員的源代碼豆同。fp 是 __main_block_func_0 函數指針番刊,賦給 impl.FuncPtr,通過 impl.FuncPtr 即可調用 block 函數影锈。__block_impl結構體的 isa 成員使用_NSConcreteStackBlock初始化芹务,這里的_NSConcreteStackBlock是什么蝉绷?首先要理解 Objective-C 類和對象的實質。其實枣抱,所謂 Block 就是 Objective-C 對象熔吗。在 /usr/include/objc/runtime.h 中 isa 的聲明:

typedef struct objc_object {
    Class isa;
} *id;

isa 保持該類的結構體實例指針。_NSConcreteStackBlock 是 Block 結構體存儲類型佳晶。

Block 存儲區(qū)

  • _NSConcreteStackBlock:存儲在棧上桅狠,超出作用域被銷毀。
  • _NSConcreteGlobalBlock:存儲在程序的數據區(qū)域中轿秧。當 block 定義在全區(qū)或者 block 沒有截獲外部變量時中跌,block 存在數據區(qū)。
  • _NSConcreteMallocBlock:存儲在由 malloc 函數分配的堆中淤刃。使用此種方式晒他,可在 block 作用域外訪問 block,引用計數為 0 時被銷毀逸贾。

截獲自動變量

int main()
{
    int val = 3;
    void (^blk)(void) = ^{int i = val;};
    
    blk();
    
    return 0;
}

那么 __main_block_impl_0 結構體變成了

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int val;
}

多了一個成員變量 val陨仅,val 被截獲了。所謂的截獲自動變量值铝侵,就是 block 所使用的外部變量被保存到 Block 的結構體實例中灼伤。

__block

block 不能修改外部變量,但如果在聲明變量的時候加上關鍵字 __block 就可在 block 內部修改外部變量咪鲜。這是如何實現的狐赡?看代碼:

int main()
{
    __block int val = 3;
    void (^blk)(void) = ^{val = 1;};
    
    blk();
    
    return 0;
}

轉換后的代碼多了一個結構體聲明

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

val 被轉換成一個結構體,其中__forwarding 是一個指向自身的指針疟丙,當 block 被拷貝時颖侄,該指針指向堆上的結構體實例。而__main_block_impl_0 val 變成對這個結構體的引用:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val;
}

因此可以在 block 內部修改外部的變量享郊。

同時览祖,改變的還有__main_block_desc_0,多了兩個成員變量 copy 和 dispose炊琉,在拷貝 block 的時候使用到展蒂。

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

變量作用域結束時,棧上的 __block 變量和 Block 也被廢棄苔咪,復制到堆上的 __block 變量和 Block 在變量作用域結束時不受影響锰悼。

__block用結構體成員變量__forwarding可以實現無論__block變量配置在棧上還是堆上時都能夠正確地訪問__block變量。

Block 循環(huán)引用

如果在 Block 中使用附有 __strong 修飾符的對象团赏,那么當 Block 從棧復制到堆時箕般,該對象為 Block 所持有,容易引起循環(huán)引用舔清。如下代碼

typedef void (^blk_t)(void);

@interface MyObject : NSObject {
    blk_t blk_;
    id obj_;
}
@end

@implementation MyObject

- (id)init {
    self = [super init];
    
    blk_ = ^{NSLog(@"obj_ = %@", obj_);};
        
    return self;
}

@end

編譯器會產生警告:Capturing 'self' strongly in this block is likely to lead to a retain cycle丝里。但很多時候編譯器無法判斷是否產生循環(huán)引用可柿,我們在使用 block 的時候要特別小心,避免循環(huán)引用可以使用 __weak 修飾符丙者。

   id __weak obj = obj_;
    blk_ = ^{NSLog(@"obj_ = %@", obj);};
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市营密,隨后出現的幾起案子械媒,更是在濱河造成了極大的恐慌,老刑警劉巖评汰,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纷捞,死亡現場離奇詭異,居然都是意外死亡被去,警方通過查閱死者的電腦和手機主儡,發(fā)現死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惨缆,“玉大人糜值,你說我怎么就攤上這事∨髂” “怎么了寂汇?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長捣染。 經常有香客問我骄瓣,道長,這世上最難降的妖魔是什么耍攘? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任榕栏,我火速辦了婚禮,結果婚禮上蕾各,老公的妹妹穿的比我還像新娘扒磁。我一直安慰自己,他們只是感情好示损,可當我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布渗磅。 她就那樣靜靜地躺著,像睡著了一般检访。 火紅的嫁衣襯著肌膚如雪始鱼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天,我揣著相機與錄音碾褂,去河邊找鬼法褥。 笑死,一個胖子當著我的面吹牛会烙,可吹牛的內容都是我干的负懦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼柏腻,長吁一口氣:“原來是場噩夢啊……” “哼纸厉!你這毒婦竟也來了?” 一聲冷哼從身側響起五嫂,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤颗品,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后沃缘,有當地人在樹林里發(fā)現了一具尸體躯枢,經...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年槐臀,在試婚紗的時候發(fā)現自己被綠了锄蹂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡水慨,死狀恐怖得糜,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情晰洒,我是刑警寧澤掀亩,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站欢顷,受9級特大地震影響槽棍,放射性物質發(fā)生泄漏。R本人自食惡果不足惜抬驴,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一炼七、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧布持,春花似錦豌拙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至胧卤,卻和暖如春唯绍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背枝誊。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工况芒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人叶撒。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓绝骚,卻偏偏與公主長得像耐版,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子压汪,可洞房花燭夜當晚...
    茶點故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內容