美文网首页
关于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的那点儿事

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