Block实现原理

  • Block是带有自动变量值的匿名函数;
  • 带有自动变量值在Block中表现为截获自动变量值
  • 自动变量值截获只能保存执行Block语法瞬间的值,保存后就不能改写该值;
  • 向截获的自动变量赋值会产生编译错误,但使用截获的值不会产生任何问题;
  • 使用__block说明符的自动变量可在Block中赋值,该变量称为__block变量;
  • Block其实就是OC对象;

一、Block的实质

使用 clang -rewrite-objc main.m将包含Block语法的源代码转换成C++的源代码。


#import <Foundation/Foundation.h>

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

转换后的C++源码:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr; //指向Block的实现
};

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

参数__cself是指向__main_block_impl_0结构体的指针变量。

struct __main_block_impl_0 *__cself;

该结构体的声明如下:

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; // 函数指针
};

第二个成员变量是Desc指针,其__main_block_desc_0结构体如下:

static struct __main_block_desc_0 {
  size_t reserved; // 预留区
  size_t Block_size; // Block大小
} ;

下面是包含这些结构体的__main_block_impl_0结构体的构造函数:

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

下面是构造函数的调用:


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

将上面的转换部分删除之后,具体如下:

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的调用转化为以下源码:

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

将上面的转换部分删除之后,具体如下:

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

这就是简单的使用函数指针调用函数。由Block语法转换的__main_block_func_0函数的指针被赋值成员变量FuncPtr中。此外,__main_block_func_0函数的参数
__cself指向Block值。在调用该函数的源码中可以看出Block正是作为参数进行了传递。
成员变量isa的初始化如下:

 impl.isa = &_NSConcreteStackBlock;

_NSConcreteStackBlock相当于class_t结构体实例。将Block作为OC对象处理时,关于该类的信息位于_NSConcreteStackBlock中。
Block的实质就是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的值,转换之后的源码如下:


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结构体中。而没有使用的自动变量不会被追加。

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

下面来看Block匿名函数的实现,初始源码如下:

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

该源码转换为以下函数:

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

由上可知:所谓截获自动变量值意味在执行Block语法时,Block语法表达式所使用的自动变量值被保存到Block的结构体实例(即Block自身)中。

三、全局变量、全局静态变量和局部静态变量

#import <Foundation/Foundation.h>

// 全局变量
int global_val = 1990; 
// 全局静态变量
static int static_global_val = 1011;

int main(int argc, const char * argv[]) {
   
     // 局部静态变量
    static int static_val = 1994;
    
    void (^blk)(void) = ^{
        global_val *= 2;
        static_global_val *= 3;
        static_val *= 4;
    };

    blk();
    return 0;
}

在上面的示例中,定义了三个变量,一个全局变量global_val,一个静态全局变量 static_global_val,一个局部静态变量static_val,然后在Block语法中修改这三个变量的值。该源码转换后如下:

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和静态全局变量 static_global_val的访问和转换前完全相同。而对局部静态变量static_val的访问转换如下:

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

使用局部静态变量static_val的指针对其进行访问,将static_val的指针传递给__main_block_impl_0结构体的构造函数并保存。

四、__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变量被转换成__Block_byref_val_0结构体类型的自动变量,即栈上生成的__Block_byref_val_0结构体实例。该结构体声明如下:

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

下面是给__block变量赋值的源码转换:

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中的局部静态变量赋值时,使用了指向该静态变量的指针。而向 __block变量赋值则更复杂。Block的__main_block_impl_0结构体实例持有指向 __block变量的__Block_byref_val_0结构体实例的指针。
__Block_byref_val_0结构体实例的成员变量__forwarding持有指向该实例自身的指针。通过__forwarding访问成员变量val,可以保证 __block变量无论配置在栈上还是堆上都可以正确的访问__block变量。这是因为 __block变量的实例在栈上时,__forwarding是指向自身的指针,当 __block变量从栈上拷贝到堆上时,__forwarding将指向复制到堆上的__block变量实例。
__block变量的__Block_byref_val_0结构体并不在Block的__main_block_impl_0结构体中,是为了在多个Block中使用 __block变量。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容