美文网首页
__weak、__strong、__block修饰Block的内

__weak、__strong、__block修饰Block的内

作者: 再好一点点 | 来源:发表于2021-11-02 17:46 被阅读0次

    关于block的上一篇文章Block内部实现

    先贴一段代码,接下来根据这段代码逐步分析__weak、__strong、__block这些关键字的作用

    一. 查看__weak、__strong、__block在这些修饰符下的源码

    main函数实现
    typedef void (^Block) (void);
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            int no = 20;
            __block int age = 10;
            NSObject *object = [[NSObject alloc] init];
            __weak NSObject *weakObject = object;
            
            Block block = ^{
                age = 20;
                
                NSLog(@"%d", no);
                NSLog(@"%d", age);
                NSLog(@"%p", weakObject);
            };
            
            struct __main_block_impl_0* blockImpl = (__bridge struct __main_block_impl_0*)block;
            block();
          
            age = 30;
        }
        return 0;
    }
    
    以下代码是将OC代码转换为C/C++代码的结果
    1. 使用__block修饰以后age变成了__Block_byref_age_0这种数据结构,是一个OC对象,含有isa指针,__forwarding指针指向自己,结构体大小__size,真正用来存储age数值的变量。
    struct __Block_byref_age_0 {
      void *__isa;
    __Block_byref_age_0 *__forwarding;
     int __flags;
     int __size;
     int age;
    };
    
    2. __main_block_impl_0这种数据结构比最简单的block多了三个参数,分别是int no;NSObject *__weak weakObject;__Block_byref_age_0 *age;,no值传递,age默认使用强引用。weakObject使用__weak修饰是因为外部使用了弱引用。如果外部不使用__weak修饰,默认会使用__strong修饰,也就是强引用。
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int no;
      NSObject *__weak weakObject;
      __Block_byref_age_0 *age; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _no, NSObject *__weak _weakObject, __Block_byref_age_0 *_age, int flags=0) : no(_no), weakObject(_weakObject), age(_age->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    3. __main_block_func_0这个函数主要就是调用block需要执行的具体函数。可以看到访问age的时候使用了(age->__forwarding->age) = 20;这种方式,而不是直接使用age->age,这是为了保证block复制到堆上以后无论是在栈上还是在堆上的block都可以访问到同一份block捕获的堆上的变量。
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_age_0 *age = __cself->age; // bound by ref
        int no = __cself->no; // bound by copy
        NSObject *__weak weakObject = __cself->weakObject; // bound by copy
        
        (age->__forwarding->age) = 20;
        
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d47e2d_mi_0, no);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d47e2d_mi_1, (age->__forwarding->age));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d47e2d_mi_2, weakObject);
    }
    
    4. __main_block_copy_0、_Block_object_assign在捕获对象的时候进行对象的内存管理。如果是__block修饰的类型就是强引用,如果是对象类型根据外部的修饰类型而定,如果外部使用__strong修饰就是强引用,如果是使用__weak这里边就使用弱引用。可以看到这里BLOCK_FIELD_IS_BYREFBLOCK_FIELD_IS_OBJECT修饰类型是不同的。
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
        _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
        _Block_object_assign((void*)&dst->weakObject, (void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    
    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->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    
    5. __main_block_desc_0 这里比最简单的block多了两个函数指针,分别是copydispose。函数实现就是上边4的两个函数。
    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};
    
    6. main函数删除了多余的部分如下所示。
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            int no = 20;
    
            __Block_byref_age_0 age = {(void*)0,&age, 0, sizeof(__Block_byref_age_0), 10};
    
            NSObject *object = objc_msgSend(objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
            __attribute__((objc_ownership(weak))) NSObject *weakObject = object;
    
            Block block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, no, weakObject, &age, 570425344);
            block->FuncPtr(block);
    
            (age.__forwarding->age) = 30;
        }
        return 0;
    }
    

    如上可以看到__block int age = 10;变成了 __Block_byref_age_0 age = {(void*)0,&age, 0, sizeof(__Block_byref_age_0), 10};
    age = 30;变成了(age.__forwarding->age) = 30;
    这里就是在栈上访问age最终存储的地方,为了保持和block内部访问到的是同一个地方所以使用了age.__forwarding先找到堆上的age这个结构体的地址,然后在访问堆上该结构体的变量age

    二. 设置断点查看age具体存放的内存地址

    将以下这些结构体放在main函数上边,用来获取成员变量的内存地址的时候使用。

    struct __Block_byref_age_0 {
        void *__isa;
        struct __Block_byref_age_0 *__forwarding;
        int __flags;
        int __size;
        int age;
    };
    
    struct __main_block_desc_0 {
        size_t reserved;
        size_t Block_size;
        void (*copy)(void);
        void (*dispose)(void);
    };
    
    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;
        struct __Block_byref_age_0 *age;
    };
    

    由下边可知直接获取age的地址和使用&(blockImpl->age->__forwarding->age)获取的地址是一样的,也就是说直接使用age就是访问的copy到堆上的__Block_byref_age_0结构体里边的age

    (lldb) p/x &age
    (int *) $0 = 0x000000010065a598
    (lldb) p/x &(blockImpl->age)
    (__Block_byref_age_0 **) $1 = 0x000000010065a560
    (lldb) p/x &(blockImpl->age->age)
    (int *) $2 = 0x000000010065a598
    (lldb) p/x &(blockImpl->age->__forwarding->age)
    (int *) $3 = 0x000000010065a598
    

    三. __block修饰符

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

    四. __block的内存管理

    1. 当block在栈上时,并不会对__block变量产生强引用

    2. 当block被copy到堆时
      a) 会调用block内部的copy函数
      b) copy函数内部会调用_Block_object_assign函数
      c) _Block_object_assign函数会对__block变量形成强引用(retain)


      __block的内存管理-引用
    3. 当block从堆中移除时
      a) 会调用block内部的dispose函数
      b) dispose函数内部会调用_Block_object_dispose函数
      c) _Block_object_dispose函数会自动释放引用的__block变量(release)

    __block的内存管理-释放

    四. __block的__forwarding指针

    __block的__forwarding指针

    五. 对象类型的auto变量、__block变量

    1. 当block在栈上时,对它们都不会产生强引用

    2. 当block拷贝到堆上时,都会通过copy函数来处理它们
      __block变量(假设变量名叫做a)
      a) _Block_object_assign((void)&dst->a, (void)src->a, 8/BLOCK_FIELD_IS_BYREF/);

    3. 对象类型的auto变量(假设变量名叫做p)
      a) _Block_object_assign((void)&dst->p, (void)src->p, 3/BLOCK_FIELD_IS_OBJECT/);

    4. 当block从堆上移除时,都会通过dispose函数来释放它们
      __block变量(假设变量名叫做a)
      a) _Block_object_dispose((void)src->a, 8/BLOCK_FIELD_IS_BYREF*/);

    5. 对象类型的auto变量(假设变量名叫做p)
      a) _Block_object_dispose((void)src->p, 3/BLOCK_FIELD_IS_OBJECT*/);

    六. 被__block修饰的对象类型

    1. 当__block变量在栈上时,不会对指向的对象产生强引用

    2. 当__block变量被copy到堆时
      a) 会调用__block变量内部的copy函数
      b) copy函数内部会调用_Block_object_assign函数
      c) _Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)

    3. 如果__block变量从堆上移除
      a) 会调用__block变量内部的dispose函数
      b) dispose函数内部会调用_Block_object_dispose函数
      c) _Block_object_dispose函数会自动释放指向的对象(release)

    下一篇 Block 解决循环引用(ARC、MRC)

    相关文章

      网友评论

          本文标题:__weak、__strong、__block修饰Block的内

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