美文网首页
关于block的那点儿事

关于block的那点儿事

作者: 危险地带_浅笑 | 来源:发表于2018-08-10 15:15 被阅读44次

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

相关文章

  • 关于block的那点儿事

    1,变量截获 前几天朋友给我出了个block的题目 相信有一定经验的同学都知道打印结果是12,为了深入了解其中的缘...

  • block 那点儿破事

    在日常开发中,我们常常会定义block将一段代码保存起来等待合适的时机调用来完成一系列的操作(hehe...出bu...

  • 关于Block的那些事

    理解“块”这一个概念 块可以实现闭包。这项语言特性是作为“扩展”而加入GCC编译器中的,从技术上讲,这是个C语言层...

  • iOS面试之Block大全

    Block Block内容如下: 关于Block 截获变量 __block修饰符 Block的内存管理 Block...

  • iOS面试之Block模块

    Block Block内容如下: 关于Block 截获变量 __block修饰符 Block的内存管理 Block...

  • 关于Block使用的二三事

    引子 我们今天不讲Block这个语法,因为作为一个合格的开发者,如果你不知道,那么实在是说不过去。接下来我就当大家...

  • 关于考研的那点儿事

    三天前的现在,我正做完一套试卷准备打开网络对答案的时候,看到群里的消息刷刷地在网上冒。打开群,才发现考研初试...

  • 关于销售的那点儿事

    一、时间没有等我,是你忘了带我走,我们就这样迷散在陌生的风雨里,从此天各一方,两两相忘。 二、一个人至少拥有一个梦...

  • 关于追星那点儿事

    关于追星女孩那点儿事。 橘子以前不疯狂追星的时候,总是看不懂身边的追星女孩儿,她们在痴迷的时候简直像中毒似的。 橘...

  • 关于早恋那点儿事

    人与人之间的爱恋从一出生就伴随着我们,从一开始对母亲的依恋,到幼儿园我们慢慢会有自己的玩伴,不管是同性的...

网友评论

      本文标题:关于block的那点儿事

      本文链接:https://www.haomeiwen.com/subject/jqojpxtx.html