Block变量捕获

变量分类

在了解变量捕获之前,我们首先了解一下C语言中变量的分类。C语言中变量分为三类

  • 全局变量: 作用域在全局,哪个地方都能调用
  • 局部变量:作用域在大括号中,只能在大括号内调用
    • 局部自动变量 auto 关键字修饰
    • 局部静态变量 static 关键字修饰

block变量捕获分类

// 全局变量
int a = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 自动变量
        auto int b = 20;
        // 自动变量 前边使用auto关键字修饰 系统默认变量是auto 所以我们一般省略auto关键字
        int c = 20;
        // 静态变量
        static int d = 30;
    }
    return 0;
}

自动变量前边使用auto关键字修饰 系统默认变量是auto 所以我们一般省略auto关键字,上方的b和c都是自动变量。表示静态变量的static关键字是不能省略的

block变量捕获机制

变量类型 是否能捕获 访问方式
全局变量 直接访问
局部自动变量(auto) 值传递
局部静态变量(static) 地址传递

全局变量捕获

block其实不用捕获全局变量,标题只是为了分类讨论,大家不用纠结。

// 全局变量
int num = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void) = ^{
            NSLog(@"num is %d", num);
        };
        block();
    }
    return 0;
}

看下C++源码 关于如果获取C++源码 请自行百度或者查看我的上一篇文章Block本质,文章中简单说明了一下clang指令用法

#pragma clang assume_nonnull end
// 这里就是定义的全局变量
int num = 10;
// 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;
  }
};
// block内部调用方法
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    // 打印的num值
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_r_tmjs697cnd9ry_p3npfzzc0000gn_T_main_35fc61_mi_0, num);
}

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

// main函数
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

我们可以看到在 __main_block_func_0 调用NSLog访问num的时候,是直接访问的全局的那个num变量。block没有捕获这个全局变量num

局部自动auto变量捕获

以下变量捕获我们只展示不同的内容

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int num = 10;
        void(^block)(void) = ^{
            NSLog(@"num is %d", num);
        };
        num = 20;
        block();
    }
    return 0;
}
// 最终打印结果
// num is 10

编译后

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  // 这里就是捕获的变量类型
  int num;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int num = 10;
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
        num = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
main函数

如箭头所指,block实现方法是把外部auto变量num的值传递进来了,然后在__main_block_impl_0内部定义中多了一个 int num变量。并且初始化的时候__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, int flags=0) : num(_num) 是把传递过来的_num赋值给了num
调用过程

// block 内部调用
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
/* ⚠️⚠️⚠️⚠️ 喂喂喂 注意看这里 ⚠️⚠️⚠️⚠️
*当block内部调用方法的时候是拿到block的成员变量num 赋值下方临时定义的num 然后调用NSlog方法 打印临时的num值
*/
  int num = __cself->num; // bound by copy
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_r_tmjs697cnd9ry_p3npfzzc0000gn_T_main_e46c57_mi_0, num);
}

当调用block的时候 我们可以看到int num = __cself->num; block把自己生成的成员变量num赋值给了新变量num,然后调用NSLog的时候直接使用了这个新变量num。main函数中 num = 20 是在block捕获变量num=10之后,所以不会修改block内部捕获的num=10这个值。所以最终结果num=20。

局部自动static变量捕获

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 静态变量
        static int num = 10;
        void(^block)(void) = ^{
            NSLog(@"num is %d", num);
        };
        num = 20;
        block();
    }
    return 0;
}
// 打印结果
// num is 20

编译后

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  // 这里就是捕获的变量类型
  int *num;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_num, int flags=0) : num(_num) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};


int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        static int num = 10;
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &num));
        num = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

在这里插入图片描述

注意看箭头,这里传递的是num变量的地址,我们也可以仔细观察,在__main_block_impl_0内部定义中多了一个 int *num变量,这里的num也是地址指针。这就说明static变量捕获的是变量的地址而不是变量的值
调用

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

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_r_tmjs697cnd9ry_p3npfzzc0000gn_T_main_2d4953_mi_0, (*num));
        }

通定义一样,调用的时候也是调用的num变量的地址,这也就是说只要num指针指向的那片内存空间的值发生变化的时候,block内部也会发生改变,所以这次最终打印结果为 num is 20.

__block 修饰的变量

block修饰的变量实际上是非常重要的,我可能会单独写一篇文章介绍一下,在这里只是简单做一下分析

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int num = 10;
        void(^block)(void) = ^{
            NSLog(@"num is %d", num);
        };
        num = 20;
        block();
    }
    return 0;
}

编译后

// 新生成的对象
struct __Block_byref_num_0 {
  void *__isa;
__Block_byref_num_0 *__forwarding;
 int __flags;
 int __size;
 int num;
};

// block 实现
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  // 这是捕获的类型
  __Block_byref_num_0 *num; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 10};
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
        (num.__forwarding->num) = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

我们可以看到对于__block修饰的变量,block捕获后会重新生成一个__Block_byref_num_0新类型,里边包含一个isa指针,也就是生成了一个对象类型的变量。

变量捕获与修改外部变量值

局部自动变量修改值

int num = 10;
void(^block)(void) = ^{
    num = 20; /*这句代码会引起报错*/
    NSLog(@"num is %d", num);
};

局部自动变量捕获的是变量的值,也就是上方例子中的10这个具体数值,所以block内部不能修改外部变量的值

局部静态变量修改值

static int num = 10;
void(^block)(void) = ^{
    num = 20; 
    NSLog(@"num is %d", num);
};
// num is 20

局部static变量捕获的是变量的内存地址,也就是外部num与内部num指向的是同一个地址,内部num修改了内存地址中的值,那么外部这个num也就修改了。

__block修饰的变量

__block int num = 10;
void(^block)(void) = ^{
    num = 20; 
    NSLog(@"num is %d", num);
};
// num is 20

__block把变量包装成了对象类型,这里其实也是可以修改值的,上方只是简单介绍了一下,后期可能会单独研究__block

总结

  • 全局变量不用捕获,可以直接访问
  • 局部自动auto变量,捕获的是变量的具体值,block内部无法修改外部的值。
  • 局部静static态变量,捕获的变量的地址,内部可以修改外部变量的值。
  • __block修饰的变量,block内部把变量包装成了对象,也是可以修改值的。

有什么不正确的地方,欢迎大家指正,大家加油!!!

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

推荐阅读更多精彩内容