Block基础

作者: 夜沐月 | 来源:发表于2020-08-06 22:24 被阅读0次

    block本质上是封装了函数调用以及函数调用环境的OC对象,为什么这么说呢?我们可以从底层数据结构来看.下面是创建一个block然后用clang转换后如下

    //OC中的block
     NSObject * (^tempBlock)(NSObject *) = ^ NSObject* (NSObject *argObject) {
                return [[NSObject alloc]init];
            };
            tempBlock([NSObject new]);
    
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
    // block的定义和初始化
     NSObject * (*tempBlock)(NSObject *) = ((NSObject *(*)(NSObject *))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    // block的调用
            ((NSObject *(*)(__block_impl *, NSObject *))((__block_impl *)tempBlock)->FuncPtr)((__block_impl *)tempBlock, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")));
    // block底层实现的结构体
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __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;
      }
    };
    
    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    

    从其中可以看出block内存布局结构是一个__main_block_impl_0结构体.block有两个基本变量struct __block_impl impl; struct __main_block_desc_0 Desc,struct __block_impl impl里面放着一个重要的void * isa,还有一个 void * FuncPtr执行我们要实现的函数地址 可以看出block是一个OC对象. 下面的struct __main_block_desc_0是block的描述信息比如block的大小,还可能会涉及(void(void,void))copy (void(void,void*))dispose .__main_block_impl_0在两个基本变量之后可能出会出现捕获的变量

    既然是对象,那么block也会有类型,上面代码中 impl.isa = &_NSConcreteStackBlock;可以看出,我们也可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
    NSGlobalBlock ( _NSConcreteGlobalBlock )数据区域 .data区
    NSStackBlock ( _NSConcreteStackBlock ) 堆区(从低到高分配内存)
    NSMallocBlock ( _NSConcreteMallocBlock )栈区(从高到低分配内存)
    通过打印验证

        NSLog(@"%@", [tempBlock class]);
        NSLog(@"%@", [[tempBlock class] superclass]);
        NSLog(@"%@", [[[tempBlock class] superclass] superclass]);
        NSLog(@"%@", [[[[tempBlock class] superclass] superclass] superclass]);
    
    2020-08-05 14:41:55.193996+0800 study-Block的本质[20817:2531218] __NSGlobalBlock__
    2020-08-05 14:41:55.194257+0800 study-Block的本质[20817:2531218] __NSGlobalBlock
    2020-08-05 14:41:55.194274+0800 study-Block的本质[20817:2531218] NSBlock
    2020-08-05 14:41:55.194291+0800 study-Block的本质[20817:2531218] NSObject
    

    也可以看出block是对象,并且isa是从NSObject继承来的 怎样看这三种类型呢

    ARC
      void (^globalBlock)(void) = ^{
                NSLog(@"Hello");
            };
            
            int age = 10;
            void (^mallocBlock)(void) = ^{
                NSLog(@"Hello - %d", age);
            };
            
            NSLog(@"%@ %@ %@", [globalBlock class], [mallocBlock class], [^{
                NSLog(@"%d", age);
            } class]);
    
    输出
     study-Block的本质[20930:2547011]
     __NSGlobalBlock__    
    __NSMallocBlock__ 
    __NSStackBlock__
    
    block类型分类.png

    block的类型是怎样判定呢?可以这样判定
    在MRC下没有访问auto变量是NSGlobalBlock(这种情况很少). 访问了auto变量是NSStackBlock,NSStackBlock调用了copy后变成NSMallocBlock.

    NSStackBlock栈上的Block存在一些问题,比如

    MRC
    void (^stack)(void);
    void stackFunc()
    {
        int age = 10;
        stack = ^{
            NSLog(@"stack-------%d", age);
        };
       
    }
    int main(int argc, const char *argv[]) {
        
        @autoreleasepool {
            
            stackFunc();
            stack();
        }
        return 0;
    }
    输出
    stack-------272663488
    

    因为block存储在栈上,会自动销毁.函数调用完毕后,栈上的东西销毁, stack捕获age变成垃圾数据,出现混乱.所以要把block放到堆上,由我们去管理可以使用

    MRC
    int age = 10
      void (^ stack)(void) = [ ^{
            NSLog(@"stack-------%d", age);
        } copy];
    变成 __NSMallocBlock__
    

    既然copy可以改变block的存储区域那么我们可以尝试下面

     void (^ global)(void) = [ ^{
            NSLog(@"stack-------%d", age);
        } copy];
    NSLog(@"%@",[[global copy] class]);
    int age = 10
      void (^ stack)(void) = [ ^{
            NSLog(@"stack-------%d", age);
        } copy];
    NSLog(@"%@",[[stack copy] class]);
    NSLog(@"%@",[[[stack copy] copy] class]);
    

    上述实践可以总结得出: NSStackBlock类型的block复制之后会从栈复制到堆
    NSGlobalBlock类型的block什么也不做, NSMallocBlock类型的block复制之后引用计数加1,所以开发中要想使用block都会把block保存下来.上述是在MRC下

    在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上
    block作为函数返回值时
    将block赋值给__strong指针时
    block作为Cocoa API中方法名含有usingBlock的方法参数时
    block作为GCD API的方法参数时

    那么封装了函数调用以及函数调用环境证明呢?我们先看看函数调用环境的封装,也就是block可能捕获的变量类型
    基本数据类型
    对于局部变量 block会捕获到内部,以值传递的方式进行访问
    对于static变量类型,block也会捕获到内部,以指针传递的方式进行访问
    全局变量 不进行捕获 直接进行访问

    对象类型的auto变量
    如果block是在栈上,将不会对auto变量产生强引用
    如果block被拷贝到堆上
    会调用block内部的copy函数
    copy函数内部会调用_Block_object_assign函数
    _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
    如果block从堆上移除
    会调用block内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放引用的auto变量(release)

    对象类型的auto变量什么时候释放?

           ARCBlock block;
            {
                CXWork *work = [[CXWork alloc]init];
                work.hours = 8;
               block = ^{
                    NSLog(@"------%d",work.hours);
                };
            }
            NSLog(@"work是否销毁------------");
            
    

    执行可以看到work对象并没有销毁,我们把下面代码转成c++看到block对work产生了强引用

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      CXWork *work;//block捕获的对象类型的auto变量
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, CXWork *_work, int flags=0) : work(_work) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    栈空间上的block不会保住work对象生命,如果是堆空间有能力保住work的生命
    如果是下面

           ARCBlock block;
            {
                CXWork *work = [[CXWork alloc]init];
                work.hours = 8;
              __weak CXWork *weakWork = work;
     
               block = ^{
                    NSLog(@"------%d", weakWork.hours);
                };
            }
            NSLog(@"work是否销毁------------");
    

    work会销毁

    ARCBlock block;
            {
                __strong CXWork *work = [[CXWork alloc]init];
                work.hours = 8;
                __weak CXWork *weakWork = work;
                
                block = ^{
                    NSLog(@"------%d", weakWork.hours);
                };
            }
            NSLog(@"work是否销毁------------");
    执行 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
    error: 
          cannot create __weak reference because the current deployment target does
          not support weak references
                __attribute__((objc_ownership(weak))) CXWork *weakWork = work;
    修改: __weak修改生成不了C++通过指定arc   runtime版本
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-12.0.0 main.m
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      CXWork *__weak weakWork;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, CXWork *__weak _weakWork, int flags=0) : weakWork(_weakWork) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    当block内部访问了对象类型的auto变量时,如果block是在栈上,将不会对auto变量产生强引用.
    如果block被拷贝到堆上,ARC下,block被强引用,会自动进行copy操作,

    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};
    
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    // 自动根据捕获对象 是strong还是weak对weakWork产生强引用或弱引用
    _Block_object_assign((void*)&dst->weakWork, (void*)src->weakWork, 3
    /*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->weakWork, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    

    copy函数内部会调用_Block_object_assign函数
    _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

    _Block_object_assign函数会对__block变量形成强引用(retain)
    当block从堆中移除时
    会调用block内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放引用的__block变量(release)

    CXWork *work = [[CXWork alloc] init];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    /// block 对work产生强引用 在执行代码3.0s后释放
                NSLog(@"-------%@", work);
            });
    
    __weak CXWork *weakWork = work;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    /// block 对weakWork产生弱引用 执行代码weakWork立即释放
                NSLog(@"-------%@", weakWork);
            });
    

    __block修饰符

    void (^ARCBlock)(void)
    
    int main(int argc, const char *argv[]) {
    
    @autoreleasepool {
        int age = 10;
        ARCBlock block = ^{
          // 希望在执行block代码内部把age改为20
            NSLog(@"-------%d", age);
        };
     block()
    }
    return 0
    }
    
    为什么不能直接改
    //block内执行的代码 想在__main_block_func_0函数里修改main函数里的局部变量 可以看到改不了
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    // block内部的age
      int age = __cself->age; // bound by copy
    
                    NSLog((NSString *)&__NSConstantStringImpl__var_folders_51_mgkwq5l143zb9tlp29g09_9w0000gp_T_main_5f654b_mi_0, age);
                }
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
          // 存在于main函数的栈空间
            int age = 10;
            ARCBlock block = ((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);
        }
        return 0;
    }
    
    可以把    int age = 10; 改为 static    int age = 10; block会以 int *age的形式捕获age,所以拿到指针就可以访问修改对象的内存值进行修改
    也可以用__block修改变量 __block int age = 10;不会像static一样修改变量的值
    
    

    __block可以用于解决block内部无法修改auto变量值的问题
    __block不能修饰全局变量、静态变量(static)
    编译器会将__block变量包装成一个对象

    
    struct __Block_byref_age_0 {
      void *__isa;
    __Block_byref_age_0 *__forwarding; // 内部的指针指向自己
     int __flags;
     int __size;
     int age; //  外面用到的值存储在这
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_age_0 *age; // by ref 使用 __block的变化
    //fp将来要指向的函数地址
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    // block执行代码
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_age_0 *age = __cself->age; // bound by ref
    
                    NSLog((NSString *)&__NSConstantStringImpl__var_folders_51_mgkwq5l143zb9tlp29g09_9w0000gp_T_main_9ba40c_mi_0, (age->__forwarding->age));
    // age->__forwarding->age 通过指向结构体的指针拿到forwarding在拿到age
                }
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
          // __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};
    
            ARCBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
    
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    
    block 内部有个指针指向__Block_byref_age_0结构体,结构体内部有个成员存储age,
    
    block修饰对象类型
    struct __Block_byref_str_1 {
      void *__isa;
    __Block_byref_str_1 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     NSString *str;
    };
     __block NSString *str = [[NSString alloc]init];
    底层转为结构体__Block_byref_str_1 *str;
    在block内部赋值
    (str->__forwarding->str) = (NSString *)&__NSConstantStringImpl__var_folders_51_mgkwq5l143zb9tlp29g09_9w0000gp_T_main_62a7d7_mi_0;
    
    
     NSMutableArray *array = [NSMutableArray array];
        ARCBlock block = ^{
    // 拿来用不是修改 不用加block
          [array addObject:@"123"];
         [array addObject:@"456"]
        };
     block()
    

    相关文章

      网友评论

        本文标题:Block基础

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