Block實現(xiàn)原理

  • Block是帶有自動變量值的匿名函數(shù)梳庆;
  • 帶有自動變量值在Block中表現(xiàn)為截獲自動變量值膏执;
  • 自動變量值截獲只能保存執(zhí)行Block語法瞬間的值露久,保存后就不能改寫該值毫痕;
  • 向截獲的自動變量賦值會產(chǎn)生編譯錯誤消请,但使用截獲的值不會產(chǎn)生任何問題;
  • 使用__block說明符的自動變量可在Block中賦值蛉加,該變量稱為__block變量针饥;
  • Block其實就是OC對象丁眼;

一贺辰、Block的實質(zhì)

使用 clang -rewrite-objc main.m將包含Block語法的源代碼轉(zhuǎn)換成C++的源代碼饲化。


#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    
    void (^blk)(void) = ^{
      
        printf("-------------Block------------------\n");
    };
    
    blk();
    return 0;
}

轉(zhuǎn)換后的C++源碼:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr; //指向Block的實現(xiàn)
};

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(int argc, const char * argv[]) {

    void (*blk)(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);
    return 0;
}

源碼中的Block語法:

^{ printf("-------------Block------------------\n"); };

變換后的源代碼中也含有相同的表達式:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

       printf("-------------Block------------------\n");
}

參數(shù)__cself是指向__main_block_impl_0結(jié)構(gòu)體的指針變量硫眨。

struct __main_block_impl_0 *__cself;

該結(jié)構(gòu)體的聲明如下:

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

第一個成員變量是impl巢块,其結(jié)構(gòu)體如下:

struct __block_impl {
  void *isa;
  int Flags; // 標志
  int Reserved; // 預(yù)留區(qū)
  void *FuncPtr; // 函數(shù)指針
};

第二個成員變量是Desc指針族奢,其__main_block_desc_0結(jié)構(gòu)體如下:

static struct __main_block_desc_0 {
  size_t reserved; // 預(yù)留區(qū)
  size_t Block_size; // Block大小
} ;

下面是包含這些結(jié)構(gòu)體的__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù):

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

下面是構(gòu)造函數(shù)的調(diào)用:


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

將上面的轉(zhuǎn)換部分刪除之后棚品,具體如下:

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

Block的調(diào)用轉(zhuǎn)化為以下源碼:

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

將上面的轉(zhuǎn)換部分刪除之后铜跑,具體如下:

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

這就是簡單的使用函數(shù)指針調(diào)用函數(shù)骡澈。由Block語法轉(zhuǎn)換的__main_block_func_0函數(shù)的指針被賦值成員變量FuncPtr中肋殴。此外囤锉,__main_block_func_0函數(shù)的參數(shù)
__cself指向Block值。在調(diào)用該函數(shù)的源碼中可以看出Block正是作為參數(shù)進行了傳遞护锤。
成員變量isa的初始化如下:

 impl.isa = &_NSConcreteStackBlock;

_NSConcreteStackBlock相當于class_t結(jié)構(gòu)體實例嚼锄。將Block作為OC對象處理時,關(guān)于該類的信息位于_NSConcreteStackBlock中蔽豺。
Block的實質(zhì)就是Block為Objective-C對象区丑。

二、捕獲自動變量值

如下示例:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    
    int dmy = 250;
    int val = 10;
    const char * fmt = "val = %d \n";
    
    void (^blk)(void) = ^{
        printf(fmt,val);
    };

    blk();
    return 0;
}

Block捕獲局部變量valfmt的值修陡,轉(zhuǎn)換之后的源碼如下:


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
    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 val = __cself->val; // bound by copy

        printf(fmt,val);
    }

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 argc, const char * argv[]) {

    int dmy = 250;
    int val = 10;
    const char * fmt = "val = %d \n";

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

    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

由上面的源碼可知沧侥,Block語法表達式中使用的自動變量被作為成員變量追加到__main_block_impl_0結(jié)構(gòu)體中魄鸦。而沒有使用的自動變量不會被追加宴杀。

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

下面來看Block匿名函數(shù)的實現(xiàn),初始源碼如下:

   void (^blk)(void) = ^{
        printf(fmt,val);
    };

該源碼轉(zhuǎn)換為以下函數(shù):

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

由上可知:所謂截獲自動變量值意味在執(zhí)行Block語法時拾因,Block語法表達式所使用的自動變量值被保存到Block的結(jié)構(gòu)體實例(即Block自身)中旺罢。

三旷余、全局變量、全局靜態(tài)變量和局部靜態(tài)變量

#import <Foundation/Foundation.h>

// 全局變量
int global_val = 1990; 
// 全局靜態(tài)變量
static int static_global_val = 1011;

int main(int argc, const char * argv[]) {
   
     // 局部靜態(tài)變量
    static int static_val = 1994;
    
    void (^blk)(void) = ^{
        global_val *= 2;
        static_global_val *= 3;
        static_val *= 4;
    };

    blk();
    return 0;
}

在上面的示例中扁达,定義了三個變量正卧,一個全局變量global_val,一個靜態(tài)全局變量 static_global_val跪解,一個局部靜態(tài)變量static_val炉旷,然后在Block語法中修改這三個變量的值。該源碼轉(zhuǎn)換后如下:

int global_val = 1990;
static int static_global_val = 1011;

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

        global_val *= 2;
        static_global_val *= 3;
        (*static_val) *= 4;
    }

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 argc, const char * argv[]) {

    static int static_val = 1994;

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

    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

對全局變量global_val和靜態(tài)全局變量 static_global_val的訪問和轉(zhuǎn)換前完全相同叉讥。而對局部靜態(tài)變量static_val的訪問轉(zhuǎn)換如下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_val = __cself->static_val; // bound by copy
  (*static_val) *= 4;
}

使用局部靜態(tài)變量static_val的指針對其進行訪問窘行,將static_val的指針傳遞給__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)并保存。

四图仓、__block存儲域類說明符

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
   
    __block int val = 1994;
    
    void (^blk)(void) = ^{
        val += 25;
    };
    
    blk();
    return 0;
}

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

struct __main_block_impl_0 {
 struct __block_impl impl;
 struct __main_block_desc_0* Desc;
 __Block_byref_val_0 *val; // by ref
 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
   impl.isa = &_NSConcreteStackBlock;
   impl.Flags = flags;
   impl.FuncPtr = fp;
   Desc = desc;
 }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 __Block_byref_val_0 *val = __cself->val; // bound by ref

       (val->__forwarding->val) += 25;
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

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

int main(int argc, const char * argv[]) {

   __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 1994};

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

   ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
   return 0;
}

由上面的源碼可知罐盔,__block變量被轉(zhuǎn)換成__Block_byref_val_0結(jié)構(gòu)體類型的自動變量,即棧上生成的__Block_byref_val_0結(jié)構(gòu)體實例救崔。該結(jié)構(gòu)體聲明如下:

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

下面是給__block變量賦值的源碼轉(zhuǎn)換:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 __Block_byref_val_0 *val = __cself->val; // bound by ref

       (val->__forwarding->val) += 25;
}

在給Block中的局部靜態(tài)變量賦值時翘骂,使用了指向該靜態(tài)變量的指針。而向 __block變量賦值則更復(fù)雜帚豪。Block的__main_block_impl_0結(jié)構(gòu)體實例持有指向 __block變量的__Block_byref_val_0結(jié)構(gòu)體實例的指針碳竟。
__Block_byref_val_0結(jié)構(gòu)體實例的成員變量__forwarding持有指向該實例自身的指針。通過__forwarding訪問成員變量val狸臣,可以保證 __block變量無論配置在棧上還是堆上都可以正確的訪問__block變量莹桅。這是因為 __block變量的實例在棧上時,__forwarding是指向自身的指針烛亦,當 __block變量從棧上拷貝到堆上時诈泼,__forwarding將指向復(fù)制到堆上的__block變量實例。
__block變量的__Block_byref_val_0結(jié)構(gòu)體并不在Block的__main_block_impl_0結(jié)構(gòu)體中煤禽,是為了在多個Block中使用 __block變量铐达。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市檬果,隨后出現(xiàn)的幾起案子瓮孙,更是在濱河造成了極大的恐慌,老刑警劉巖选脊,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杭抠,死亡現(xiàn)場離奇詭異,居然都是意外死亡恳啥,警方通過查閱死者的電腦和手機偏灿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钝的,“玉大人翁垂,你說我怎么就攤上這事铆遭。” “怎么了沿猜?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵枚荣,是天一觀的道長。 經(jīng)常有香客問我邢疙,道長,這世上最難降的妖魔是什么望薄? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任疟游,我火速辦了婚禮,結(jié)果婚禮上痕支,老公的妹妹穿的比我還像新娘颁虐。我一直安慰自己,他們只是感情好卧须,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布另绩。 她就那樣靜靜地躺著,像睡著了一般花嘶。 火紅的嫁衣襯著肌膚如雪笋籽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天椭员,我揣著相機與錄音车海,去河邊找鬼。 笑死隘击,一個胖子當著我的面吹牛侍芝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播埋同,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼州叠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了凶赁?” 一聲冷哼從身側(cè)響起咧栗,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎虱肄,沒想到半個月后楼熄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡浩峡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年可岂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翰灾。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡缕粹,死狀恐怖稚茅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情平斩,我是刑警寧澤亚享,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站绘面,受9級特大地震影響欺税,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜揭璃,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一晚凿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瘦馍,春花似錦歼秽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至院崇,卻和暖如春肆氓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背底瓣。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工做院, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人濒持。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓键耕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親柑营。 傳聞我的和親對象是個殘疾皇子屈雄,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

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