Block原理分析(1)

前情提要

1.閉包、Block是一個帶有自動變量值(可以截獲自動變量值)的匿名函數术吝。截獲的含義是保存該自動變量的瞬間值转捕。
2.OC中如果要改變Block截獲的外部自動變量的值园细,需要在該變量前加上__block修飾符。Swift不用昔馋,系統(tǒng)會自動處理這些問題芜繁。延伸到對象,對于Swift來說系統(tǒng)也會處理但OC不同绒极。OC下,當從外部截獲一個對象(NSMutableArray* )mArray時,在Block內調用NSMutableArray的方法(addObject:)沒問題蔬捷,因為Block捕獲的是NSMutableArray類對象用的結構體實例指針垄提。但若對其進行操作,比如賦值周拐,就會產生編譯錯誤铡俐,要在變量前加上__block。Swift下直接用就行了妥粟。
3.從C代碼的角度分析closure/block审丘。OC可以直接用clang -rewrite-objc 原文件名來分析,Swift下的clang ..我沒有找到對應的命令勾给,所以只看OC的滩报。在分析之前,先介紹兩個小概念播急。
--1.OC&Swift中的self
OC Code:

- (void) method:(int)arg {
    NSLog(@"%p %d\n", self, arg);
}
MyObject *obj = [[MyObject alloc] init];
[obj method: 10];

轉換成C Code:

void _I_MyObject_method_(struct MyObject *self, SEL _cmd, int arg) {
    NSLog(@"%p %d\n", self, arg);
}
MyObject *obj = objc_msgSend(objc_getClass("MyObject"), sel_registerName("alloc"));
obj = objc_msgSend(obj, sel_registerName("init"));
objc_msgSend(obj, sel_registerName("method:"), 10);

最后一條執(zhí)行語句脓钾,也就是[obj method: 10]轉換成objc_msgSend(obj, sel_registerName("method:"), 10)objc_msgSend(obj, sel_registerName("method:"), 10)函數根據指定的對象和函數名桩警,從對象持有類(MyObject)的結構體中檢索[XX method:10]對應的函數-->void _I_MyObject_method_(struct MyObject *self, SEL _cmd, int arg)的指針并調用可训。此時,objc_msgSend函數的第一個參數obj作為_I_MyObject_method_(struct MyObject *self, SEL _cmd, int arg)的第一個參數self進行傳遞。即self就是Myobject類的對象-obj自己捶枢。

--2.OC中的id

*id為objc_object結構體的結構體指針握截。
id在C語言中的聲明:

typedef struct objc_object {
    Class isa;
} *id;

*Class為objc_class結構體的結構體指針。
Class在C語言中的聲明:

typedef struct objc_class {
    Class isa;
} *Class;

這與objc_object結構體相同烂叔。然而谨胞,objc_object結構體和objc_class結構體歸根結底是在各個對象(id)和類(Class)的實現(xiàn)中使用的最基本的結構體。

例:

類长已,MyObject

@interface MyObject : NSObject {
    int val0;
    int val1;
}
@end

使用MyObject創(chuàng)建的對象newObject = [MyObject new],就是基于objc_object創(chuàng)建了一個該類(MyObject)的結構體實例畜眨。通過isa保持該類(MyObject)的結構體實例指針。

struct newObject {
    Class isa;
    int val0;
    int val1;
    ...;
}

而各類的結構體是就是基于objc_class的 class_t結構體术瓮。

struct class_t {
    struct class_t *isa;
    struct class_t *superclass;
    Cache cache;
    IMP *vtable;
    uintptr_t data_NEVER_USE;
    ...;
};

class_t中持有康聂,聲明的成員變量,方法的名稱胞四,方法的實現(xiàn)(函數指針)恬汁,屬性以及父類的指針,并被OC運行時庫所使用的辜伟。

對象中的isa指針和類中的isa指針的區(qū)別在于氓侧,對象的是Class類型,只負責指向他的所屬類脊另。類的isa指針是class_t類型,包含了class_t結構體中的所有內容(比如方法约巷,父class等等)偎痛,指向它自己的元類(metaclass)。他的元類中就包含了各種方法独郎,變量等等踩麦。而他的metaclass的isa指向了NSObject,superclass指向它父類的元類。以ViewController舉例:

static void OBJC_CLASS_SETUP_$_ViewController(void ) {
    OBJC_METACLASS_$_ViewController.isa = &OBJC_METACLASS_$_NSObject;
    OBJC_METACLASS_$_ViewController.superclass = &OBJC_METACLASS_$_UIViewController;
    OBJC_METACLASS_$_ViewController.cache = &_objc_empty_cache;
    OBJC_CLASS_$_ViewController.isa = &OBJC_METACLASS_$_ViewController;
    OBJC_CLASS_$_ViewController.superclass = &OBJC_CLASS_$_UIViewController;
    OBJC_CLASS_$_ViewController.cache = &_objc_empty_cache;
}

給ViewController添加了一個實例方法(instanceFunc)一個類方法(classFunc)氓癌。
Metaclass方法列表的賦值:(const struct _method_list_t *)&_OBJC_$_CLASS_METHODS_ViewController;--->OBJC_$_CLASS_METHODS_ViewController--->:

_OBJC_$_CLASS_METHODS_ViewController __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"classFunc", "v16@0:8", (void *)_C_ViewController_classFunc}}
};

實例方法被保存在了_class_ro_t _OBJC_CLASS_RO_$_ViewController這里他會賦值給_class_t OBJC_CLASS_$_ViewController也就是本類谓谦。

即元類保存了類方法。

本類保存了實例方法贪婉。

MyObject類的實例變量val0,val1被直接聲明為對象的結構體成員反粥。OC中由類生成對象就意味著,生成一個基于該類的結構體實例疲迂,這些結構體實例(對象)通過isa保持該類的結構體實例指針才顿。舉個例子就是:有3個類,C動物->C狗->C柯基尤蒿,基于柯基生成的一個對象叫做KK娜膘,那么KK這個結構體就會有一個isa指向C柯基,而這個C柯基的isa指向自己的元類。當KK發(fā)送了一個消息(狼嚎時)因為C柯基本類的class_t結構體中的cache和vtable都沒有這個方法优质,便逐級會向上傳遞竣贪,找不到再performslector等等。


介紹完兩個小概念巩螃,開始從C代碼的角度分析Block演怎,舉兩個例子,一個是不截獲自動變量值的另一個是截獲自動變量值的:

不截獲自動變量值:

.m文件下的代碼:

int main() {
    void(^blk)(void) = ^{printf("Block\n");};
    blk();
    return0;
}

clang -rewrite-objc 項目文件名.m之后避乏,摘取有用的內容:

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

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;
  }
};

static void__main_block_func_0(struct__main_block_impl_0 *__cself) {printf("Block\n");}

static struct__main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}

__main_block_desc_0_DATA= {0, sizeof(struct __main_block_impl_0)};

int main() {
    void(*blk)(void) = ((void(*)(void))&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA));
    ((void(*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return0;
}

源碼中的^{printf("Block\n");};轉換后的代碼是

static void__main_block_func_0(struct __main_block_impl_0 *__cself) {printf("Block\n");}

通過轉換后的代碼可知爷耀,通過Block使用的匿名函數實際上被作為簡單的C語言函數來處理。另外根據Block語法所屬的函數名(此處為main)和該Block語法在該函數出現(xiàn)的順序值(此處為0)來給經clang變換的函數命名(void__main_block_func_0)拍皮。該函數的參數*__cself就是指向Block值的變量歹叮,__main_block_impl_0結構體的指針。

那我們來看看__main_block_impl_0結構體铆帽,去掉構造函數后:

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

第一個成員變量是impl咆耿,他的結構體聲明是:

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

impl是虛函數列表,該結構體內存放了一個標志爹橱,包括今后版本升級所需的區(qū)域及函數指針萨螺。

第二個成員變量是Desc指針,__main_block_desc_0結構體的聲明是:

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
};

這些也如同其成員名稱所示,其結構為今后版本升級所需的區(qū)域和Block大小慰技。
這兩個成員變量看完之后椭盏,再來看一下初始化含有這些結構體的__main_block_impl_0結構體的構造函數,就是我們剛才忽略的那一部分代碼,這個代碼執(zhí)行完成意味著_cself初始化完成:

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

在分析這個結構體之前吻商,先看一下這個構造函數的調用(在 int main() {...}中):

void(*blk)(void) = ((void(*)(void))&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA));

去掉轉換部分 簡化成兩行代碼:

struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;

該源代碼將__main_block_impl_0結構體類型的自動變量(棧上生成的__main_block_impl_0結構體實例的指針)掏颊,賦值給__main_block_impl_0結構體指針類型的變量blk,對應未轉換前的代碼就是這個賦值操作:

void(^blk)(void) = ^{printf("Block\n");};

即,將Block語法生成的Block賦給Block類型變量blk。該源代碼中的Block就是__main_block_impl_0結構體類型的自動變量艾帐,即棧上生成的__main_block_impl_0結構體實例蚯舱。然后指針賦值。

接下來看看__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);結構體實例構造參數掩蛤。__main_block_func_0&__main_block_desc_0_DATA

__main_block_func_0是一個由Block語法轉換的C語言函數指針,對應的函數就是:static void __main_block_func_0(struct__main_block_impl_0 *__cself) {printf("Block\n");}

&__main_block_desc_0_DATA是作為靜態(tài)全局變量初始化的__main_block_desc_0結構體實例指針陈肛。其初始化代碼如下:

__main_block_desc_0_DATA= {0,sizeof(struct__main_block_impl_0)};

由此可知揍鸟,該源代碼使用Block,即__main_block_impl_0結構體實例的大小,進行初始化句旱。


下面綜合來看一下阳藻,棧上的Block即__main_block_impl_0結構體實例的初始化到底是怎樣完成的(相關參數展開):

struct __main_block_impl_0 {
    void *isa;
    Int Flags;
    int Reserved;
    void &FuncPtr;
    struct __main_block_desc_0* Desc;
}

該結構體會根據構造函數進行如下的初始化:

isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;

即,向內存申請空間谈撒,并將__main_block_func_0函數指針賦值給成員變量FunPtr,為后續(xù)調用做好準備腥泥。


源碼中調用blk的部分blk()對應轉換后的代碼是:

((void(*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

去掉轉換部分:

(*blk->impl.FuncPtr)(blk);

這就是簡單地使用函數指針調用函數。

階段總結

由Block語法轉換的__main_block_func_0函數的指針被賦值給棧上生成的__main_block_impl_0結構體實例的FuncPtr中,再將棧上生成的__main_block_impl_0結構體實例賦值給Block類型的變量blk,之后調用blk()啃匿。
通過觀察調用函數蛔外,__main_block_func_0函數的參數__cself指向Block值blk,調用:(*blk->impl.FuncPtr),參數:(blk)。由此看出Block正是作為參數進行了傳遞溯乒。


截獲自動變量值的:

.m文件下的代碼:

#include <stdio.h>
int main() {
    int unCapturedVariable = 100;
    int capturedVariable = 60;
    const char *fmt = "capturedVariable = %d/n";
    void(^blk)(void) = ^{printf(fmt, capturedVariable);};
    blk();
    return 0;
}

clang -rewrite-objc 項目文件名.m之后夹厌,摘取有用的內容:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int capturedVariable;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _capturedVariable, int flags=0) : fmt(_fmt), capturedVariable(_capturedVariable) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int capturedVariable = __cself->capturedVariable; // bound by copy
printf(fmt, capturedVariable);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main() {
    int unCapturedVariable = 100;
    int capturedVariable = 60;
    const char *fmt = "capturedVariable = %d/n";
    void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, capturedVariable));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

這與前面轉換的源代碼稍有差異。下面來看看不同之處裆悄。首先我們注意到矛纹,Block語法表達式中使用的自動變量(capturedVariablefmt)被作為成員變量追加到了__main_block_impl_0中。并且類型完全相同光稼,而Block語法表達式中沒有使用的自動變量(unCapturedVariable)并沒有被追加進去或南。

因此,Block的自動變量截獲只針對Block中使用的自動變量艾君。

再來看看初始化__main_block_impl_0結構體的構造函數__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _capturedVariable, int flags=0) : fmt(_fmt), capturedVariable(_capturedVariable) {...}采够,有什么不同。
在初始化結構實例時冰垄,根據傳遞給構造函數的參數對成員變量(capturedVariable和fmt)進行初始化吁恍。通過以下代碼:
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, capturedVariable));。使用執(zhí)行Block語法時的自動變量capturedVariablefmt來初始化__main_block_impl_0結構體實例(系統(tǒng)生成的Block,或者叫右邊的Block可能好理解一些)冀瓦。即在該源代碼中伴奥,__main_block_impl_0結構體實例的初始化方法如下(相關參數展開):

struct __main_block_impl_0 {
    void *isa;
    Int Flags;
    void &FuncPtr;
    struct __main_block_desc_0* Desc;
    char *fmt;
    int capturedVariable;
}

賦值:

impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
fmt = "capturedVariable = %d/n";
capturedVariable = 60;

由此可知在__main_block_impl_0結構體實例中,成員變量(fmtcapturedVariable)被外部自動變量值賦值翼闽。

下面再來看一下使用Block的匿名函數的實現(xiàn)拾徙。轉換前的代碼片段是:printf(fmt, capturedVariable);
轉換后的是:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int capturedVariable = __cself->capturedVariable; // bound by copy
printf(fmt, capturedVariable);}

在轉換后的源代碼中,截獲到__main_block_impl_0結構體實例的成員變量的自動變量(__cself->fmt__cself->capturedVariable)感局, bound by copy尼啡!這些變量(fmtcapturedVariable)在Block語法表達式執(zhí)行之前(沒初始化__main_block_impl_0之前)就已經被聲明定義在__main_block_impl_0結構體里。

總的來說询微,所謂“截獲自動變量值”意味著在執(zhí)行__main_block_func_0時崖瞭,Block語法表達式所使用的自動變量值已經被保存到Block的結構體實例(__main_block_impl_0)中了。在函數中可以直接截獲到它們撑毛,并使用书聚。


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市藻雌,隨后出現(xiàn)的幾起案子雌续,更是在濱河造成了極大的恐慌,老刑警劉巖胯杭,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驯杜,死亡現(xiàn)場離奇詭異,居然都是意外死亡做个,警方通過查閱死者的電腦和手機鸽心,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來居暖,“玉大人再悼,你說我怎么就攤上這事∠サ” “怎么了冲九?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長跟束。 經常有香客問我莺奸,道長,這世上最難降的妖魔是什么冀宴? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任灭贷,我火速辦了婚禮,結果婚禮上略贮,老公的妹妹穿的比我還像新娘甚疟。我一直安慰自己仗岖,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布览妖。 她就那樣靜靜地躺著轧拄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪讽膏。 梳的紋絲不亂的頭發(fā)上檩电,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音府树,去河邊找鬼俐末。 笑死,一個胖子當著我的面吹牛奄侠,可吹牛的內容都是我干的卓箫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼垄潮,長吁一口氣:“原來是場噩夢啊……” “哼烹卒!你這毒婦竟也來了?” 一聲冷哼從身側響起魂挂,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎馁筐,沒想到半個月后涂召,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡敏沉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年果正,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盟迟。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡秋泳,死狀恐怖,靈堂內的尸體忽然破棺而出攒菠,到底是詐尸還是另有隱情迫皱,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布辖众,位于F島的核電站卓起,受9級特大地震影響,放射性物質發(fā)生泄漏凹炸。R本人自食惡果不足惜戏阅,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望啤它。 院中可真熱鬧奕筐,春花似錦舱痘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至笆怠,卻和暖如春铝耻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蹬刷。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工瓢捉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人办成。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓泡态,卻偏偏與公主長得像,于是被迫代替她去往敵國和親迂卢。 傳聞我的和親對象是個殘疾皇子某弦,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359