美文网首页
Block详解

Block详解

作者: CoderKK | 来源:发表于2020-03-30 23:21 被阅读0次
    窥探block底层结构

    我们写下一个最简单的block使用clang指令生成对应的C\C++代码

    void (^block)(void) = ^{
        NSLog(@"Hello, World!");
    };
    block();
    

    截取关键代码如下

    struct __block_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      // 构造函数(类似于OC的init方法),返回结构体对象
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    // 封装了block执行逻辑的函数
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
                //这一句就是打印"Hello, World!"
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c60393_mi_0);
    }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    // 定义block变量
    void (*block)(void) = &__main_block_impl_0(
                                               __main_block_func_0,
                                               &__main_block_desc_0_DATA
                                               );
    
    // 执行block内部的代码
    block->FuncPtr(block);
    

    从上面代码可以看出,block本质上也是一个OC对象,内部也有个isa指针,并且内部封装了函数调用。

    block的变量捕获

    写下一个访问外部变量的block

    int age = 10;
    void (^block)(void) = ^{
        NSLog(@"age is %d",age);
    };
    block();
    

    生成C\C++代码,截取关键部分

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int age;
    };
    //block内部的函数
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int age = __cself->age; // bound by copy
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_2w_5f0_0k152c5d825xmdf9ztmr0000gn_T_main_b9df52_mi_0,age);
    }
    
    int age = 10;
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
    
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    

    我们可以观察到block结构体多了个age变量,并且在初始化block时,将外部的age变量赋值给了结构体内部这个age变量,当函数执行时,直接打印的是结构体内部的age变量。
    所以我们可以总结一下,block就是封装了函数调用以及函数调用环境的OC对象
    为了保证block内部能正常访问外部的变量,block有个变量捕获机制

    变量类型 是否捕获 访问方式
    auto局部变量 值传递
    static局部变量 指针传递
    全局变量 直接访问

    局部变量不写修饰默认就是auto变量,其实从内存上也很好理解block的捕获机制。auto局部变量在栈区,函数调用完后资源会被释放掉,而static局部变量是在程序运行过程中一直存在的(存在数据段),所以用指针随时可以找到,而全局变量本来就是在哪都可访问,根本没必要捕获。

    block的类型

    block有三种类型,可以通过class方法或者isa指针查看具体的类型,它们最终都继承自NSBlock

    类型 判断依据 存储区域 调用copy结果
    __NSGlobalBlock__ 没有访问auto变量 数据段 什么也不做
    __NSStackBlock__ 访问了auto变量 栈区 从栈区复制到堆
    __NSMallockBlock__ __NSStackBlock__调用了copy 堆区 引用计数增加

    在ARC环境下会根据情况自动将栈上的block复制到堆上,比如以下情况

    • block作为函数返回值时
    • block赋值给__strong指针时(对象类型的默认修饰就是__strong)
    • block作为Cocoa API中方法名含有usingBlock参数时
    • block作为GCD API的方法参数时
      MRC下建议用copy修饰block属性,ARC可以用strong和copy修饰block属性
    block访问对象类型的auto变量

    写下如下代码,用clang指令生成C\C++

    NSObject *obj = [[NSObject alloc] init];
    void (^block)(void) = ^{
        NSLog(@"%@",obj);
    };
    block();
    

    截取部分关键代码,可以看到
    当block访问对象类型的auto变量时,内部多了copy函数和dispose函数

    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    //定义block代码
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
    
    • 当block在栈上时,不会对auto变量产生强引用
    • 当block从栈上被拷贝到堆上时
      会调用block内部的copy函数,copy函数会调用内部的_Block_object_assign函数,该函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用或弱引用
    • 当block从堆上移除时
      block会调用内部的dispose函数,dispose函数会调用内部的_Block_object_dispose函数,该函数会释放引用的auto变量(release)
    __block修饰符

    __block可以用于解决block内部无法修改auto变量问题,__block不能用来修饰全局变量,静态变量(static)

    __block int age = 10;
    ^{
        age = 30;
    }();
    NSLog(@"age is %d",age);
    

    生成C\C++代码

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_age_0 *age; // by ref
    }
    struct __Block_byref_age_0 {
      void *__isa;
    __Block_byref_age_0 *__forwarding;
     int __flags;
     int __size;
     int age;
    };
    //__block int age = 10;
    __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
    ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344))();
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2w_5f0_0k152c5d825xmdf9ztmr0000gn_T_main_ad80c7_mi_0,(age.__forwarding->age));
    

    当我们使用__block修饰age变量时,会将age变量包转成age对象,age对象里的int age存储着最初的age值,__forwarding指针是指向age对象自己,block捕获的是age对象的地址值。从最后的一句可以看出,当我们使用__block修饰auto变量后,访问age都变成了访问age对象里的age成员变量。

    • __forwarding指针
      当block从栈上拷贝到堆上时,栈上对象的__forwarding会指向堆上的拷贝对象(block拷贝到堆上时,会将捕获的对象变量一并copy到堆上)
    block循环引用问题

    从上面我们可以看到,block访问对象类型的auto变量时有可能会产生强引用,当访问的auto变量又对block产生强引用时就会发生循环应用。举例如下

    typedef void(^MyBlock)(void);
    @interface Person : NSObject
    @property (nonatomic, copy) MyBlock myBlock;
    @end
    //main函数里面
    Person *person = [[Person alloc] init];
    person.myBlock = ^{
        NSLog(@"%@",person);
    };
    

    在ARC环境下可以使用__weak、__unsafe_unretained解决(一般使用__weak,会自动置nil)

    Person *person = [[Person alloc] init];
    //或者 __unsafe_unretained typeof(Person *) weakPerson = person;
    __weak typeof(Person *) weakPerson = person;
    person.myBlock = ^{
        NSLog(@"%@",weakPerson);
    };
    

    在MRC环境下可以使用__unsafe_unretained解决

    相关文章

      网友评论

          本文标题:Block详解

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