Block变量捕获

上一篇《Block本质的探究》是在比较简单场景之下去探究的,当然不论怎么复杂的场景,本质的一些东西是不会变的。
这一篇主要来探究一下Block的变量捕获,下面开始进入正题!

一、 Block接收参数

有下面的一段代码, block接收两个参数,这种方式有什么不一样的呢?

 //block定义
void (^myBlock)(int, int) = ^(int num1, int num2){
      NSLog(@"Hello Block!-- a=%d - b = %d", num1, num2);
 };
 //block调用
myBlock(10, 20);

编译后

 //block定义
 void (*myBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0(
    __main_block_func_0, 
&__main_block_desc_0_DATA));

   //block调用
 ((void (*)(__block_impl *, int, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 10, 20);

---------- 去除强制转换后 ------

//block定义
 void (*myBlock)(int, int) = &__main_block_impl_0(
      __main_block_func_0,
     &__main_block_desc_0_DATA)
);
//block调用
myBlock->FuncPtr(myBlock, 10, 20);

__main_block_func_0这个结构体封装函数增加了两个参数

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int num1, int num2) {
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_bee7ad_mi_0, num1, num2);
}

从上面不难发现,定义的时候传进去的参数没有什么变化,只有在调用的时候把参数分别放到了FuncPtr(__main_block_func_0)函数的第二和第三个参数。

二、 Block捕获外部变量

在探究Block捕获外部变量之前,我们先了解一下Block的变量捕获机制

1、 Block变量捕获机制

Block为了能够正常访问外部变量,有了一个变量捕获机制。

变量类型 捕获到block内部 访问方式
局部变量 auto 值传递
局部变量 static 指针传递
全部变量 static 直接访问

补充:

  • 局部变量默认就是auto修饰,写的时候我们可以省略,即 int num1 = 10; 其实就是auto int num1 = 10;`
  • auto: 自动变量,离开作用域就销毁
2、 Block捕获auto局部变量

我们来看一段代码:

int num1 = 10;
//定义一个block
void (^myBlock)(void) = ^{
     NSLog(@"Hello Block!-- num1 =%d", num1);
};
 num1 = 20; //修改一下num1的值
//block调用
myBlock();  //输出Hello Block!-- num1 =10

编译后

int num1 = 10;
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0(
    (void *)__main_block_func_0, 
    &__main_block_desc_0_DATA, 
    num1
));

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

从上面可以看出num1的值直接传到__main_block_impl_0(..)这个构造函数里了。那既然传递到构造函数里,不难猜测,里面的__main_block_impl_0结构体肯定会有些变化了,下面来探究一下这些变化。

看下__main_block_impl_0结构体:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int num1;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num1, int flags=0) : num1(_num1) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 首先可以看到多了一个参数同名的成员 int num1,这个值就是用来存储外面传进来参数值。
  • 构造函数后面多了个 :num1(_num1),这个是c++语法,类似把参数_num1赋值给num1,即 num1 = _num1

再看下__main_block_func_0封装函数调用的函数

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int num1 = __cself->num1; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_2a5abb_mi_0, num1);
}
  • 相比没有捕获外部变量,多了int num1 = __cself->num1; 也就是通过__cself拿到__main_block_impl_0里面的成员 num1的值,而原来__main_block_impl_0里面的num1存储的值就10。

而当执行num1 = 20这句代码的时候,改变的仅仅是修改了外面定义的num1变量的值。这也是为什么最后打印出来的值依旧是 10的原因。//输出Hello Block!-- num1 =10

3、 Block捕获static局部变量

我们来看下面的代码:

auto int num1 = 10;
static int age = 10;
//定义一个block
void (^myBlock)(void) = ^{
   NSLog(@"Hello Block!-- num1 =%d -- age = %d", num1,  age);
};
//block调用
num1 = 20;
age = 20;
//block调用
myBlock(); //输出Hello Block!-- num1 =10 -- age = 20

编译一下,看看有什么不一样

 //定义一个block
  void (*myBlock)(void) = ((void (*)())&__main_block_impl_0(
        (void *)__main_block_func_0, 
        &__main_block_desc_0_DATA, 
        num1,
       &age));
//block调用
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

我们看到定义的时候传给构造函数的参数不一样了:

  • auto 修饰的变量,传进去就num1的值
  • static 修饰的变量,传进去的age的地址值

再看结构体__main_block_impl_0里面的变化

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

  • 捕获了两个外部变量
  • 与参数num1同名的成员num1来接收外面传进来的
  • 与参数age同名的 *age来接收外面传进来的地址值
  • : num1(_num1), age(_age) 分别进行赋值操作。

再看下__main_block_func_0封装函数执行逻辑的函数

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int num1 = __cself->num1; // bound by copy
  int *age = __cself->age; // bound by copy
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_af9218_mi_0, 
            num1,
         (*age)
    );
}

block函数调用的时候, 通过__cself拿到对应的成员的值

  • int num1 = __cself->num1;, 取出的是值
  • int *age = __cself->age;,取出的是地址值
  • 注意看NSLog函数里面如何使用的这两个值:
    (*age)这个操作就是取出*age所指向内存地址变量的值

所以,到这里应该不难理解为什么程序运行之后输出的结果是Hello Block!-- num1 =10 -- age = 20

三、 Block访问全局变量

下面来看看 Block内部访问全局变量的情况,我们来看下面的代码:

int num1 = 10;
static int age = 10;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       //定义一个block
        void (^myBlock)(void) = ^{
            NSLog(@"Hello Block!-- num1 =%d -- age = %d", num1,  age);
        };
        num1 = 20;
        age = 20;
       //block调用
        myBlock();  
    }
    return 0;
}

编译以后:

#pragma clang assume_nonnull end

int num1 = 10;
static int age = 10;

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) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_f263c3_mi_0, 
      num1, 
      age);
}

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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0(
            (void *)__main_block_func_0, 
            &__main_block_desc_0_DATA));
        num1 = 20;
        age = 20;
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    }
    return 0;
}

通过查看编译后的代码,我们可以发现:

  • __main_block_impl_0结构体里面并没有增加新的成员变量,即没有捕获外面的变量
  • __main_block_impl_0 (..)构造函数也没有接收其他额外的参数。
  • __main_block_func_0 (..)封装了函数执行逻辑的函数也没有接收额外的参数
  • NSLog (..)函数调用的时候,直接访问了num1 和 age

所以,为什么程序运行输出的结果是:Hello Block!-- num1 =20 -- age = 20也是不难理解的。

关于Block捕获外部变量的探究就先到这里啦!!!

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

推荐阅读更多精彩内容