Block 之 变量捕获

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制,即捕获外部变量。

前言: 搞清成员变量、局部变量、全局变量
屏幕快照 2018-10-29 上午12.20.32.png

此处再转载一篇文,介绍auto,static,register,extern修饰变量的区别:https://www.cnblogs.com/Lyush/archive/2013/01/09/2852625.html
下次有空再自己整理(小心心标记住,回头一定要整理)

1、对局部变量捕获
<1>auto变量(自动变量)
    //auto变量(自动变量) : 离开作用域会自动销毁
    //注意:一般局部变量前面有个auto的标示来修饰,只不过auto常被省略不写
    int age = 10;// auto int age = 10;
    void(^myBlock1)(void) = ^{
        NSLog(@"age is %d",age);
    };
    age = 20;
    
    myBlock1();

=================================================
打印结果:[14995:3091224] age is 10

为什么打印结果是10而不是20呢?
带着疑问,我们通过通过clang 命令来观察ViewController.m的.cpp文件:

//此处struct为myBlock1的底层结构
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  //已经将age这个值传进来了,block内部的age这个值就保存为10了
  int age;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

可以得出结论:当定义myBlock1的时候,已经捕获了age为10的这样一个变量,当将age重新赋值为20的时候,myBlock1的结构没有发生改变,所以打印结果为10。

<2>static变量(静态变量)
    //static变量(静态变量): 全局只存在一份,一直存储在内存中
    static int height = 160;
    void(^myBlock2)(void) = ^{
        NSLog(@"height is %d",height);
    };
    height = 165;
  
    myBlock2();

=================================================
打印结果:[14995:3091224] height is 165

这次为什么打印结果是165而不是160呢?
同样,我们通过通过clang 命令来观察ViewController.m的.cpp文件:

//此处struct为myBlock2的底层结构
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  //注意:传递的是height的地址(指针传递),将height这个值的存放地址传进来了,block访问的时候是去该地址取值的
  int *height;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int *_height, int flags=0) : height(_height) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

可以得出结论:当定义myBlock2的时候,也捕获了height变量,只不过变量被static修饰,所以捕获的是height这个变量的存储地址,当myBlock2通过地址去取值的时候,自然取到的是height的当前值,所以打印结果为165。

2、对全局变量不捕获

上面讲的是两种类型的局部变量,下面看全局变量

//age、height为全局变量
int age = 10;
static int height = 160;

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    void(^myBlock3)(void) = ^{
        NSLog(@"age is %d",age);
        NSLog(@"height is %d",height);
        
    };
    age = 20;
    height = 165;
    
    myBlock3();
}

=================================================
打印结果:BlockTest[2222:225124]  age is 20
         BlockTest[2222:225124] height is 165

通过通过clang 命令来观察ViewController.m的.cpp文件:


int age = 10;
static int height = 160;

// @implementation ViewController

//此处struct为myBlock3的底层结构
//可以看出并没有捕获age、height这个变量
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
        //此处直接访问全局变量age和height
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_cp_f1q8npcs2b91f_9szlk2t6f00000gn_T_ViewController_a91b1c_mi_0,age);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_cp_f1q8npcs2b91f_9szlk2t6f00000gn_T_ViewController_a91b1c_mi_1,height);

    }

static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));

    void(*myBlock3)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA));
    age = 20;
    height = 165;

    ((void (*)(__block_impl *))((__block_impl *)myBlock3)->FuncPtr)((__block_impl *)myBlock3);
}

可以得出结论:对于全局变量,block不会捕获。而是直接访问全局变量。

3、拓展问题

对于下面的这样的写法,myBlock会捕获变量age吗?

#import "ViewController.h"

@interface ViewController ()
@property(nonatomic,assign)int age;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    void(^myBlock)(void) = ^{
        NSLog(@"_____%@",self.age);
    };
    
    myBlock();
}
@end

让我们看看他的编译源码:(认真看注释哟)

//此处struct为myBlock的底层结构
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
    //这里面捕获的并不是单纯的一个属性,而是ViewController这个类
    ViewController *self;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, ViewController *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
  ViewController *self = __cself->self; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_cp_f1q8npcs2b91f_9szlk2t6f00000gn_T_ViewController_94764c_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("age")));
    }
static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
  void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_0};


//这个就是我们ViewController里面的viewDidLoad方法
//一般OC函数的底层转成的C语言函数,他默认接受两个参数: ViewController * self 和 SEL _cmd
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));

    //可以看出 在定义myBlock的时候,会将self传入__ViewController__viewDidLoad_block_impl_0里面
    //然后在myBlock的底层结构__ViewController__viewDidLoad_block_impl_0里面,self就是这个传入的self了
    void(*myBlock)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, self, 570425344));

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


static int _I_ViewController_age(ViewController * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_ViewController$_age)); }
static void _I_ViewController_setAge_(ViewController * self, SEL _cmd, int age) { (*(int *)((char *)self + OBJC_IVAR_$_ViewController$_age)) = age; }

所以,对于这种block 里面调用某个类、或者某个类的成员变量的时候,block都是会捕获这个类的。
因为这种情况下,在底层都是会将self指代的这个类作为参数传入block 的构造函数,参数作为一个局部变量,所以block当然会捕获变量的啦。

4、总结
  • <1>block捕获哪些变量?哪些变量不捕获?
    局部变量捕获 :其中auto局部变量捕获它的值,static局部变量捕获他的地址
    全局变量不捕获
    屏幕快照 2018-10-28 下午11.44.09.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容