美文网首页
__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