关于block的那点儿事

1,变量截获

前几天朋友给我出了个block的题目

-(void)method
    int multiplier = 6;
    int(^Block)(int) = ^int(int num){
        return num * multiplier;
    };
    multiplier = 4;
    NSLog(@"result is %d",Block(2));
}

相信有一定经验的同学都知道打印结果是12,为了深入了解其中的缘由,就开始了下面的一系列的操作了,利用命令clang -rewrite-objc -fobjc-arc MyBlock.m生成一个MyBlock.cpp,打开文件找到相应的代码

struct __MyBlock__method_block_impl_0 {
//保存block信息的结构体
  struct __block_impl impl;
  //关于block描述
  struct __MyBlock__method_block_desc_0* Desc;
  //参数
  int multiplier;
  __MyBlock__method_block_impl_0(void *fp, struct __MyBlock__method_block_desc_0 *desc, int _multiplier, int flags=0) : multiplier(_multiplier) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

从上面代码中可以看出来,block创建的时候,变量multiplier被直接传入block的构造函数中,所以下面修改multiplier的值的时候根本不会修改构造函数里面的参数了。这时候我不禁在想,如果传入的是其他类型的数据呢?于是乎,写了另一个方法

-(void)method
    __unsafe_unretained id unsafe_obj = nil;
    __strong id strong_obj = nil;
    static int static_var = 3;
    int my_var = 3;
    self.str = @"hello";
    NSString * s = @"123";
    void(^Block)(void)=^{
        NSLog(@"局部变量__unsafe_unretained id数据类型:%@",unsafe_obj);
        NSLog(@"局部变量__strong id数据类型:%@",strong_obj);
        NSLog(@"静态变量 static int数据类型:%d",static_var);
        NSLog(@"基本数据类型:%d",my_var);
        NSLog(@"全局变量字符串值:%@",self.str);
        NSLog(@"局部变量字符串的值:%@",s);
    };
    strong_obj = @"123";
    self.str = @"world";
    s=@"hello";
    Block();
}

打印结果为

2018-08-10 13:42:38.488653+0800 Block底层调用[1278:88416] 局部变量__unsafe_unretained id数据类型:(null)
2018-08-10 13:42:38.488771+0800 Block底层调用[1278:88416] 局部变量__strong id数据类型:(null)
2018-08-10 13:42:38.488846+0800 Block底层调用[1278:88416] 静态变量 static int数据类型:3
2018-08-10 13:42:38.488917+0800 Block底层调用[1278:88416] 基本数据类型:3
2018-08-10 13:42:38.489053+0800 Block底层调用[1278:88416] 全局变量字符串值:world
2018-08-10 13:42:38.489302+0800 Block底层调用[1278:88416] 局部变量字符串的值:123

利用命令clang -rewrite-objc -fobjc-arc MyBlock.m生成一个MyBlock.cpp,打开文件找到相应的代码

struct __MyBlock__method_block_impl_0 {
  struct __block_impl impl;
  struct __MyBlock__method_block_desc_0* Desc;
  __unsafe_unretained id unsafe_obj;
  __strong id strong_obj;
  int *static_var;
  int my_var;
  MyBlock *const __strong self;
  NSString *__strong s;
  __MyBlock__method_block_impl_0(void *fp, struct __MyBlock__method_block_desc_0 *desc, __unsafe_unretained id _unsafe_obj, __strong id _strong_obj, int *_static_var, int _my_var, MyBlock *const __strong _self, NSString *__strong _s, int flags=0) : unsafe_obj(_unsafe_obj), strong_obj(_strong_obj), static_var(_static_var), my_var(_my_var), self(_self), s(_s) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __MyBlock__method_block_func_0(struct __MyBlock__method_block_impl_0 *__cself) {
    __unsafe_unretained id unsafe_obj = __cself->unsafe_obj; // bound by copy
    __strong id strong_obj = __cself->strong_obj; // bound by copy
    int *static_var = __cself->static_var; // bound by copy
    int my_var = __cself->my_var; // bound by copy
    MyBlock *const __strong self = __cself->self; // bound by copy
    NSString *__strong s = __cself->s; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_kq_1y5vtnrx03d7m9x_0610rbdm0000gn_T_MyBlock_e16049_mi_2,unsafe_obj);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_kq_1y5vtnrx03d7m9x_0610rbdm0000gn_T_MyBlock_e16049_mi_3,strong_obj);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_kq_1y5vtnrx03d7m9x_0610rbdm0000gn_T_MyBlock_e16049_mi_4,(*static_var));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_kq_1y5vtnrx03d7m9x_0610rbdm0000gn_T_MyBlock_e16049_mi_5,my_var);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_kq_1y5vtnrx03d7m9x_0610rbdm0000gn_T_MyBlock_e16049_mi_6,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("str")));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_kq_1y5vtnrx03d7m9x_0610rbdm0000gn_T_MyBlock_e16049_mi_7,s);
}

从上面代码发现,block构造函数对局部变量的所有权修饰符一起截获了,但是没有截获全局变量。因此我们也就知道,在block创建之后修改哪些值会影响到block初始化后的值了。

2,__block

-(void)method{
    int multiplier = 6;
    int(^Block)(int) = ^int(int num){
        multiplier += num;
        return multiplier;
    };
    NSLog(@"result is %d",Block(2));
}

关于这段代码,有一定开发经验的人都知道编译器会报这个错误

Variable is not assignable (missing __block type specifier)

是的,如果用编译器,所有人都能够一眼看出来需要将代码改成

-(void)method{
    __block int multiplier = 6;
    int(^Block)(int) = ^int(int num){
        multiplier += num;
        return multiplier;
    };
    NSLog(@"result is %d",Block(2));
}

那么添加了__block,到底发生了什么变化呢?
变量截获可以知道静态全局变量全局变量静态全局变量不需要使用__block,而在block修改局部变量就需要用__block;利用命令clang -rewrite-objc -fobjc-arc MyBlock.m生成一个MyBlock.cpp,找到相应代码块如下

struct __MyBlock__method_block_impl_0 {
  struct __block_impl impl;
  struct __MyBlock__method_block_desc_0* Desc;
  __Block_byref_multiplier_0 *multiplier; // by ref
  __MyBlock__method_block_impl_0(void *fp, struct __MyBlock__method_block_desc_0 *desc, __Block_byref_multiplier_0 *_multiplier, int flags=0) : multiplier(_multiplier->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

struct __Block_byref_multiplier_0 {
    void *__isa;
     __Block_byref_multiplier_0 *__forwarding;
    int __flags;
    int __size;
    int multiplier;
};

由上面代码可以看出,用__block修饰过的局部变量multiplier其实已经变成了一个结构体__Block_byref_multiplier_0,由于该结构体有一个isa指针,所以实际上multiplier变成了一个对象;相当于代码变成了multiplier=4;=> multiplier.__forwarding->multiplier;。所以当block内部修改multiplier的值,相当于通过multiplier对象的__forwarding指针修改其对应的值,由于此时__forwarding指向的是变量自己,因此修改的值就是栈上面multiplier的值

block内存管理

block包括以下三种类型

  1. _NSConcreteGlobalBlock //全局block 处于已初始化数据区
  2. _NSConcreteStackBlock //栈上block 处于栈上
  3. _NSConcreteMallocBlock //堆上block 处于堆上

block的copy操作产生的结果如下

block类别 copy结果
_NSConcreteMallocBlock 增加引用计数
_NSConcreteStackBlock
_NSConcreteGlobalBlock 数据区 什么也不做

当对栈上面的block进行copy操作时候,实际上是复制了一份block和__block变量放在堆里面,这也是为什么在MRC中,如果对栈上面的block进行copy之后不手动释放就会产生内存泄露。
看如下代码:

-(void)method{
    __block int multiplier = 6;
    self.Block = ^int(int num){
        multiplier += num;
        return multiplier;
    };
    multiplier = 4;
    [self executeBlock];
}

-(void)executeBlock{
    NSLog(@"%d",self.Block(4));
}

打印结果为:

2018-08-10 15:04:40.695608+0800 Block底层调用[2238:218584] 8

当copy完成之后,栈上面__block变量__forwarding指向堆上面的__block变量,而堆上面_block变量__forwarding指向自身。当进行copy完成之后,再出修改multiplier,实际是修改了堆上面_block变量的值。

生成指定架构的c++源文件命令:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m -o xxx-arm64.cpp

生成ARC,指定运行时的源文件命令:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0  xxx.m -o xxx.cpp

当然循环引用也是比较常见的,但是大部分同学都知道了,此处就不在赘述了。

参考资料Block技巧与底层解析

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

推荐阅读更多精彩内容

  • 前言 Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这...
    小人不才阅读 3,753评论 0 23
  • 1 Block机制 (Very Good) Block技巧与底层解析 http://www.jianshu.com...
    Kevin_Junbaozi阅读 4,032评论 3 48
  • Block基础回顾 1.什么是Block? 带有局部变量的匿名函数(名字不重要,知道怎么用就行),差不多就与C语言...
    Bugfix阅读 6,744评论 5 61
  • 一、Objective-C发展史 Objective-C从1983年诞生,已经走过了30多年的历程。随着时间的推移...
    没事蹦蹦阅读 5,811评论 12 34
  • 小鱼喜欢上了另一个部门的男同事。 那个男孩英挺帅气,工作表现突出,见到他的第一眼,小鱼就被牢牢吸引。 小鱼一向行事...
    如果是什么果阅读 561评论 4 6