关于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技巧与底层解析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

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

友情链接更多精彩内容