美文网首页
iOS Block本质笔记

iOS Block本质笔记

作者: 山杨 | 来源:发表于2021-10-30 19:30 被阅读0次

    OC中定义block

    void(^myBlock)(NSString *) = ^(NSString *param){
    };
    

    block访问外部参数

    int age = 10;
    static NSString *name = @"Andy";
    NSString *address = @"India";
    
    void(^myBlock)(NSString *) = ^(NSString *param){
        
        name = @"Alandy";
        NSLog(@"name %@, age %@, address %@", name, @(age), address);
    };
    

    OC转C++分析

    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;
      NSString **name;
      int age;
      NSString *address;
      __main_block_impl_0(
      void *fp, 
      struct __main_block_desc_0 *desc, 
      NSString **_name, int _age, NSString *_address, int flags=0) : name(_name), age(_age), address(_address) {
        // name(_name)的意思是把传入的_name赋值给外面的成员name
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    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)};
    
    static void 
    __main_block_func_0(struct __main_block_impl_0 *__cself, NSString *param) {
    
      NSString **name = __cself->name; // bound by copy
      int age = __cself->age; // bound by copy
      NSString *address = __cself->address; // bound by copy
    
      (*name) = (NSString *)&__NSConstantStringImpl__var_main_2;
    
      NSLog(
      &__NSConstantStringImpl__var_main_3, (*name), 
      objc_msgSend(objc_getClass("NSNumber"),sel_registerName("numberWithInt:"),age), 
      address);
    }
    static void 
    __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src) {
      _Block_object_assign((void*)&dst->name, (void*)src->name, 3);
      _Block_object_assign((void*)&dst->address, (void*)src->address, 3);
    }
    static void 
    __main_block_dispose_0(struct __main_block_impl_0 *src) {
        _Block_object_dispose((void*)src->name, 3);
        _Block_object_dispose((void*)src->address, 3);
    }
    
    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};
    
    int age = 10;
    static NSString *name = (NSString *)&__NSConstantStringImpl__var_main_0;
    NSString *address = (NSString *)&__NSConstantStringImpl__var_main_1;
    void(*myBlock)(NSString *) = 
    ((void (*)(NSString *))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &name, age, address, 570425344));
    -----------
    简化一下 myBlock
    void(*myBlock)(NSString *) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &name, age, address, 570425344);
    
    • block的变量捕获机制

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


    int a = 1;等价于 auto int a = 1;
    自动变量,离开作用域就销毁
    

    static修饰的变量一直存在内存中不会被释放,在block内部可以直接访问,被捕获到block内部的static变量会变成*或**类型,例如:block中捕获了static修饰的name

    struct __main_block_impl_0 {
      ...
      NSString **name;
      ...
    };
    
    • block的类型

      • block的本质是一个OC对象,内部有一个isa指针
      • block是封装了函数调用以及函数环境的OC对象


    验证一下:

    void(^myBlock)(NSString *) = ^(NSString *param){};
    [myBlock class] 的返回值是 __NSGlobalBlock__
    Class superCls = [myBlock superclass];
    superCls 的值是 NSBlock
    [superCls superclass] 的返回值是 NSObject
    [[superCls superclass] superclass] 的返回值为 null
    

    那么block的类型就是__NSGlobalBlock__了吗?不止如此
    换一种block的用法:

    int height = 173;
    void(^myBlock)(int) = ^(int param){
        NSLog(@"height %d", height);
    };
    

    再通过NSLog打印出的结果是__NSMallocBlock__
    再换一种block的用法:

    NSLog(@"%@", [^{NSLog(@"%d", height);} class]);
    

    这种直接方式打印出的结果是__NSStackBlock__
    通过clang编译出来的block结果与打印出来的结果不同,以NSLog运行时打印的结果为准,原因与llvm的编译器有关系

    通过这样的方式得到的block有3种类型分别是

    注意:
    (在ARC模式下,由于编译器增加了一些东西,导致在block内部访问了auto变量的时候,block变成了__NSMallocBlock__,在MRC下是__NSStackBlock__)
    • 在ARC模式下把block作为属性使用时用copy和strong修饰都可以
    @property (nonatomic, copy) void (^block)(void);
    or
    @property (nonatomic, strong) void (^block)(void);
    
    • ARC模式下系统带有block的方法都是默认进行了copy操作的例如
    -[NSArray enumerateObjectsUsingBlock:[block copy]];
    

    有使用到__weak等修饰词的代码,OC文件转C++的时候需要额外添加一些内容

    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m
    

    1. 只要是在栈上的block(__NSStackBlock__)都不会对外部的对象进行强引用
    2. 如果block被拷贝到堆上
      • 会调用block中的__main_block_copy_0函数
      • __main_block_copy_0函数又会调用_Block_object_assign函数
      • _Block_object_assign中会根据外部的auto变量使用的修饰符(__weak__strongunsafe_unretained)做出相应的操作(强引用、弱引用)
    3. 如果block从堆上移除
      • 会调用block内部的__main_block_dispose_0函数
      • __main_block_dispose_0会调用_Block_object_dispose函数去释放block中引用的auto变量,类似release
    • 用__block修饰变量

    被__block修饰过得变量结构会发生变化

    __block int height = 173;
    本质是:
    __attribute__((__blocks__(byref))) __Block_byref_height_0 height = {(void*)0,(__Block_byref_height_0 *)&height, 0, sizeof(__Block_byref_height_0), 173};
    简化一下:
    __Block_byref_height_0 height =
    {
      0,
      &height,
      0,
      sizeof(__Block_byref_height_0), 
      173
    };
    
    struct __Block_byref_height_0 {
      void *__isa;
      __Block_byref_height_0 *__forwarding;
      int __flags;
      int __size;
      int height;
    };
    ---------------- 分隔线 ----------------
    __block NSObject *obj = [NSObject new];
    本质是:
    __attribute__((__blocks__(byref))) __Block_byref_obj_1 obj = {(void*)0,(__Block_byref_obj_1 *)&obj, 33554432, sizeof(__Block_byref_obj_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"))};
    简化一下:
    __Block_byref_obj_1 obj  = 
    {
      0, 
      &obj, 
      33554432, 
      sizeof(__Block_byref_obj_1), 
      __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, 
      objc_msgSend(objc_getClass("NSObject"), sel_registerName("new"))
    };
    
    struct __Block_byref_obj_1 {
      void *__isa;
    __Block_byref_obj_1 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     NSObject *obj;
    };
    
    ^{   
        obj = nil;
        height = 175;
    }
    这段block中的代码本质是:
    在__main_block_func_0中的
    (obj->__forwarding->obj) = __null;
    (height->__forwarding->height) = 175;
    

    有个一直没解决的问题:代码中使用__block修饰NSString *之后,转换C++文件会报错,不知道怎么处理!!!求指教

    • 创建__block修饰的NSArray对象
      __block NSArray *arr = @[@"1111"];
    block中修改arr的值
    ^{        
       arr = @[@"22222"];
    };
    

    转换成C++

    __Block_byref_arr_2 arr = 
    {
     0,
     &arr, 
     33554432, 
     sizeof(__Block_byref_arr_2), 
     __Block_byref_id_object_copy_131, 
     __Block_byref_id_object_dispose_131, 
     objc_msgSend(
       objc_getClass("NSArray"), 
       sel_registerName("arrayWithObjects:count:"), 
       __NSContainer_literal(1U, &__NSConstantStringImpl__var_main_0).arr, 1U)
    };
    
    • __main_block_func_0
    (arr->__forwarding->arr) = objc_msgSend(
     objc_getClass("NSArray"), 
     sel_registerName("arrayWithObjects:count:"), 
     __NSContainer_literal(1U, &__NSConstantStringImpl__var_main_1).arr, 1U
    );
    

    综上看来被__block修饰的变量会把变量包装成一个结构体来使用

    __block int height = 173;
    
    struct __Block_byref_height_0 {
      void *__isa;
      __Block_byref_height_0 *__forwarding;
      int __flags;
      int __size;
      int height;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_height_0 *height; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_height_0 *_height, int flags=0) : height(_height->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    // 基本数据类型的变量如果没有被__block修饰过,不会产生copy和dispose函数
    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中执行的代码
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_height_0 *height = __cself->height;
      (height->__forwarding->height) = 175;
    }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
      _Block_object_assign(&dst->height, src->height, 8);
    }
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {
      _Block_object_dispose(src->height, 8);
    }
    
    被__block修饰的height变量的实际地址和__Block_byref_height_0中的height相同。
    • __block的内存管理

      1. 当block在栈上(__NSStackBlock__),并不会对__block变量产生强引用
      2. 当block被copy到堆时(__NSMallocBlock__)
        • 会调用block内部的__main_block_copy_0函数
        • copy函数内部会调用_Block_object_assign函数
        • _Block_object_assign函数会对__block修饰的变量形成强引用(retain)或弱引用(仅限于ARC模式下会retain,MRC模式不会retain)
      3. 当block从堆上移除
        • 会调用block内部的__main_block_dispose_0函数
        • dispose函数内部会调用_Block_object_dispose函数
        • _Block_object_dispose函数会自动释放引用的__block变量(release)
    • block的循环引用问题

      ARC模式下

      • __weak__unsafe_unretained解决
      __weak typeof(self) weakSelf = self;    
      __unsafe_unretained typeof(self) unSelf = self;
      
      self.block = ^{
            
          weakSelf.name = @"__weak";
          unSelf.name = @"__unsafe_unretained";
      };
      
      • __block解决(必须要执行block才能释放)
      __block YSObject *bSelf = self;
      self.block = ^{
      
          bSelf.name = @"__block";
          bSelf = nil;
      };
      self.block();
      

      MRC模式下

      • __unsafe_unretained解决(MRC下没有弱引用)
      __unsafe_unretained typeof(self) unSelf = self;
      self.block = ^{
            
          unSelf.name = @"__unsafe_unretained";
      };
      
      • __block解决
      __block YSObject *bSelf = self;
      self.block = ^{
      
          bSelf.name = @"__block";
      };
      
      • 使用__strong确保对象没有被释放
      __weak typeof(self) weakSelf = self;
      self.block = ^{
        // 确保weakSelf有效,使用weakSelf无法直接访问_name
        __strong strongSelf = weakSelf;
        strongSelf->_name = @"__weak";
      };
      

    • block的常见面试题

      1. block的本质
      2. __block的原理
      3. 如何处理循环引用问题,ARC和MRC下的处理方式有哪些不同,为什么?

    相关文章

      网友评论

          本文标题:iOS Block本质笔记

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