Block

作者: 六月的某一个早晨 | 来源:发表于2019-06-25 19:20 被阅读0次
    • block本质上也是一个OC对象,它内部也有一个isa指针。
    • block是封装了函数调用以及函数调用环境的OC对象。
      Block底层结构.png
    • block底层结构就是__main_block_impl_0结构体,内部包含了impl结构体和Desc结构体以及外部需要访问的变量,block将需要执行的代码放到一个函数里,impl内部的FuncPtr指向这个函数的地址,通过地址调用这个函数,就可以执行block里面的代码了。Desc用来描述block,内部的reserved作保留,Block_size描述block占用内存。

    block的变量捕获(capture)

    为了保证block内部能够正常访问外部的变量,block有个变量捕获机制

    • 由于作用域的问题,全局变量不用捕获到block内部,block执行代码的函数可直接访问
    • 局部变量的定义和需要执行的代码在不同的函数里,如果想跨函数访问局部变量,需要把局部变量捕获到block中存起来,再在执行代码的函数从block中取出捕获的局部变量进行访问。


      block的变量捕获.png
    auto int age = 10;
    static int height = 10;    
    void (^block)(void) = ^{
        NSLog(@"age is %d,height is %d",age,height);
    };        
    age = 20;
    height = 20;        
    block();
    -------------------------------------------------
    output: age is 10,height is 20
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int age; // 值传递
      int *height; // 指针传递
    }
    

    auto变量和static变量访问方式的不同,是由于auto变量随时可能自动销毁,通过值传递访问;而static变量会一直在内存中,可通过指针(地址)传递访问。

    • 同样的,self也会被block捕获,是因为所有的OC方法转化成C语言函数,底层会默认传递两个参数,self_cmd,也是局部变量。
    - (void)test
    {
        void(^block)(void) = ^{
            NSLog(@"----%p",self);
        };
        block();
    }
    -------------------------------------------------
    static void _I_YCPerson_test(YCPerson * self, SEL _cmd) {
        void(*block)(void) = ((void (*)())&__YCPerson__test_block_impl_0((void *)__YCPerson__test_block_func_0, &__YCPerson__test_block_desc_0_DATA, self, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    
    struct __YCPerson__test_block_impl_0 {
      struct __block_impl impl;
      struct __YCPerson__test_block_desc_0* Desc;
      YCPerson *self;
    };
    

    block的类型

    block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都继承自NSBlock类型。

    __NSGlobalBlock__ ( _NSConcreteGlobalBlock )
    __NSStackBlock__ ( _NSConcreteStackBlock )
    __NSMallocBlock__ ( _NSConcreteMallocBlock )
    
    block类型 环境
    NSGlobalBlock 没有访问auto变量
    NSStackBlock 访问了auto变量
    NSMallocBlock NSStackBlock调用了copy

    每一种类型的block调用copy后的结果如下所示

    Block的类 副本源的配置存储域 复制效果
    _NSConcreteGlogalBlock 程序的数据区域 什么也不做
    _NSConcreteStackBlock 从栈复制到堆
    _NSConcreteMallocBlock 引用计数器增加
    • 在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:
      • block作为函数返回值时
      • 将block赋值给__strong指针时
      • block作为Cocoa API中方法名含有usingBlock的方法参数时
      • block作为GCD API的方法参数时

    对象类型的auto变量

    • 在使用clang转换OC代码为C++代码时,可能会遇到以下问题:
      cannot create __weak reference in file using manual reference
      解决方案:支持ARC、指定运行时系统版本,比如
      xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc-fobjc-arc -fobjc-runtime=ios-8.0.0main.m
    YCPerson *person = [[YCPerson alloc] init];
    person.age = 10;        
    __weak YCPerson *weakPerson = person;
        MyBlock block = ^{
        NSLog(@"-----%d",weakPerson.age);
    };
    -------------------------------------------------
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      YCPerson *__weak weakPerson;
    }
    
    YCPerson *person = [[YCPerson alloc] init];
    person.age = 10;
    //        __weak YCPerson *weakPerson = person;
    MyBlock block = ^{
        NSLog(@"-----%d",person.age);
    };
    -------------------------------------------------
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      YCPerson *__strong person;
    }
    

    当block内部访问了对象类型的auto变量时

    • 如果block是在栈上,不论是ARC还是MRC环境,或者对外部的auto变量是强引用还是弱引用,都不会对auto变量产生强引用。
    • 如果block被拷贝到堆上
      1、会调用block内部的copy函数
      2、copy函数内部会调用_Block_object_assign函数
      3、_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak)产生强引用还是弱引用。
    • 如果block从堆上移除
      1、会调用block内部的dispose函数
      2、dispose函数内部会调用_Block_object_dispose函数
      3、_Block_object_dispose函数会自动释放引用的auto变量,类似于release
    函数 调用时机
    copy函数 栈上的Block复制到堆上
    dispose函数 堆上的Block被废弃时

    __block修饰符

    • 一般情况下,对被截获对象进行赋值操作需要添加__block修饰符(赋值≠使用)
    • 对变量进行赋值时
      • 对局部变量(基本数据类型对象类型),需要__block修饰符
      • 静态局部变量全局变量静态全局变量,不需要__block修饰符
    • __block可以用来解决block内部无法修改auto变量的问题
    • 编译器会将__block变量包装成一个对象(__Block_byref_age_0结构体),结构体内部__forwarding是指向自身的指针,内部还存储着外部auto变量的值
      __block修饰基本数据类型
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // 三种auto变量
            int no = 20; // 基本数据类型的auto变量,不需要内存管理,不会生成copy函数和dispose函数
    
            __block int age = 10; // __block修饰基本数据类型变量,需要内存管理,会生成copy函数和dispose函数
    
            NSObject *object = [[NSObject alloc] init]; // 对象类型的auto变量,需要内存管理,不会生成copy函数和dispose函数
            __weak NSObject *weakObject = object;
         
            // 刚开始block内存在栈上,在ARC环境下,一旦block被强引用着,会对栈上的block进行copy操作,会拷贝到堆上
            // block如果是在栈上,对象类型的auto变量object和__block变量age产生的都是弱引用,不是强引用;如果block被copy到堆时,都会通过copy函数来处理它们
            MyBlock block = ^{ 
                age = 20; // 修改age变量
                NSLog(@"%d",no);
                NSLog(@"%d",age);
                NSLog(@"%p", weakObject);
            };
            block();
        }
        return 0;
    }
    -------------------------------------------------
    struct __Block_byref_age_0 {
        void *__isa;
        struct __Block_byref_age_0 *__forwarding;
        int __flags;
        int __size;
        int age;
    };
    
    struct __block_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
    };
    
    struct __main_block_desc_0 {
        size_t reserved;
        size_t Block_size;
    };
    
    struct __main_block_impl_0 {
        struct __block_impl impl;
        struct __main_block_desc_0* Desc;
        struct __Block_byref_age_0 *age;
    };
    
    struct __Block_byref_age_0 {
      void *__isa;
    __Block_byref_age_0 *__forwarding;
     int __flags;
     int __size;
     int age; // 结构体内部会存储着age值
    };
    
      // 此处block会捕获三个auto变量
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int no; // 将no值直接存储
      NSObject *__weak weakObjc; // 访问对象类型的auto变量,会在内部存储该类型的指针变量,__weak or __strong取决于外部如何访问
      __Block_byref_age_0 *age; // 访问__Block变量,会将age包装到__Block_byref_age_0结构体中,block内部保留一个引用这个结构体的指针,指针会指向__Block_byref_age_0结构体
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _no, NSObject *__weak _weakObjc, __Block_byref_age_0 *_age, int flags=0) : no(_no), weakObjc(_weakObjc), age(_age->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_age_0 *age = __cself->age; // 通过block中age指针拿到指向结构体的指针
      int no = __cself->no; // bound by copy
      NSObject *__weak weakObjc = __cself->weakObjc; // bound by copy
                (age->__forwarding->age) = 20;
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_1_y0148j4xd12nhm7z0r2gj00000gn_T_main_161189_mi_0,no);
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_1_y0148j4xd12nhm7z0r2gj00000gn_T_main_161189_mi_1,(age->__forwarding->age));
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_1_y0148j4xd12nhm7z0r2gj00000gn_T_main_161189_mi_2,weakObjc);
            }
    
    // block从栈拷贝到堆时调用
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    // __block,第三个参数传递8(BLOCK_FIELD_IS_BYREF),__block变量不存在强弱引用之分,就是强引用
    _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    // 对象类型的auto变量object,第三个参数传递3(BLOCK_FIELD_IS_OBJECT),如果外部通过弱(强)引用访问OC对象,那_Block_object_assign对OC对象产生的就是弱(强)引用
    _Block_object_assign((void*)&dst->weakObjc, (void*)src->weakObjc, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    // block从堆中移除调用
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_dispose((void*)src->weakObjc, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    // 访问对象类型的auto变量,会生成以下两个函数,对内部访问的对象进行内存管理操作,访问基本数据类型不会生成这两个函数
    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的forwarding指针

    __block的forwarding指针
    (age->__forwarding->age) = 20;
    
    • 上,__block结构体中的__forwarding指针指向自己,一旦复制到上,栈上的__block结构体中的__forwarding指针会指向堆上的__block结构体,堆上__block结构体中的__forwarding还是指向自己。假设age是栈上的变量,age->__forwarding会拿到堆上的__block结构体,age->__forwarding->age会把20赋值到堆上,不论是栈上还是堆上的__block结构体,都能保证20赋值到堆的结构体里。

    block循环引用问题

    • 用__weak、__unsafe_unretained解决
    // __weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil,若再次访问此变量,不会产生错误。
    __weak typeof(self) weakSelf = self;
       self.block = ^{
       NSLog(@"%d",weakSelf.age);
    };
    // __unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变,若再次访问此变量,容易产生野指针错误。
    __unsafe_unretained typeof(self) weakSelf = self;
       self.block = ^{
       NSLog(@"%d",weakSelf.age);
    };
    
    __weak、__unsafe_unretained解决循环引用.png
    • 用__block解决
    __block YCPerson *person = [[YCPerson alloc] init];
    person.age = 10;
    person.block = ^{
        NSLog(@"%d",person.age);
        person = nil;
    };
    person.block(); // 必须要调用block,执行block内代码,将对象置为nil
    
    用__block解决循环引用.png

    相关文章

      网友评论

          本文标题:Block

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