Ios@IONIC精通blockiOS

__block的实现和内存管理

2019-01-30  本文已影响1人  YY_Lee

__block的内存管理

当block使用外部变量时,是不能直接在block内修改这些变量的。我们用__block修饰变量后就能够修改了。但需要说明一点__block只能用于auto变量无法修改,__block不能修饰全局变量、静态变量。

先看一段代码:

- (void)blockModifyVariable {
    __block int a = 10;
    __block Person *person = [Person new];
    person.name = @"mm";
    BlockDemo block = ^{
        a = 11;
        person = [Person new];
        person.name = @"modified";
        NSLog(@"%d---%@",a,person.name);//打印结果:11---modified
    };
    block();
}

下面是block通过clang转换成C++的代码 :

struct __ViewController__blockModifyVariable_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__blockModifyVariable_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __Block_byref_person_1 *person; // by ref
  __ViewController__blockModifyVariable_block_impl_0(void *fp, struct __ViewController__blockModifyVariable_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_person_1 *_person, int flags=0) : a(_a->__forwarding), person(_person->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

加了__block修饰后,block结构体里面也是增加了两个成员变量,不同的是并不是直接捕获外部的变量,而是增加了两个__Block_byref开头的对象。下面看这两个对象:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __Block_byref_person_1 {
  void *__isa;
__Block_byref_person_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *__strong person;
};

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

对比两个结构体,对象类型的person内部多了copy和dispose这两个管理内存的函数。对象内部分别有和外部变量名称一致的a和person。并且能看出__Block_byref_person_1是强引用着person的。__Block_byref_person_1是否强引用person要取决于指向外部的person变量是用什么修饰符修饰,如果是用weak或者__unsafe_unretained,那这里就是弱引用。

下面是方法blockModifyVariable转换后的代码:

static void _I_ViewController_blockModifyVariable(ViewController * self, SEL _cmd) {
    
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a ={
        (void*)0,
        (__Block_byref_a_0 *)&a,
        0,
        sizeof(__Block_byref_a_0),
        10
        };
    
    __attribute__((__blocks__(byref))) __Block_byref_person_1 person = {
        (void*)0,
        (__Block_byref_person_1 *)&person,
        33554432,
        sizeof(__Block_byref_person_1),
        __Block_byref_id_object_copy_131,
        __Block_byref_id_object_dispose_131,
        ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"))
    };
    
    ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)(person.__forwarding->person), sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_5067e2_mi_0);
    
    BlockDemo block = ((void (*)())&__ViewController__blockModifyVariable_block_impl_0((void *)__ViewController__blockModifyVariable_block_func_0, &__ViewController__blockModifyVariable_block_desc_0_DATA, (__Block_byref_a_0 *)&a, (__Block_byref_person_1 *)&person, 570425344));
    
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

可以看出用__block修饰后将变量a和person包装成__Block_byref_a_0和__Block_byref_person_1。block初始化时将这两个对象赋值给了block内部的成员变量a和person。

block的成员变量desc:

static struct __ViewController__blockModifyVariable_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __ViewController__blockModifyVariable_block_impl_0*, struct __ViewController__blockModifyVariable_block_impl_0*);
  void (*dispose)(struct __ViewController__blockModifyVariable_block_impl_0*);
} __ViewController__blockModifyVariable_block_desc_0_DATA =
{ 0,
  sizeof(struct __ViewController__blockModifyVariable_block_impl_0),
  __ViewController__blockModifyVariable_block_copy_0,
  __ViewController__blockModifyVariable_block_dispose_0
};


static void __ViewController__blockModifyVariable_block_copy_0(struct __ViewController__blockModifyVariable_block_impl_0*dst, struct __ViewController__blockModifyVariable_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __ViewController__blockModifyVariable_block_dispose_0(struct __ViewController__blockModifyVariable_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);}

desc中用copy函数和dispose函数来管理包装后的对象a和person的内存;

下面看看block执行代码块时是如何修改和取值的:

static void __ViewController__blockModifyVariable_block_func_0(struct __ViewController__blockModifyVariable_block_impl_0 *__cself) {
    __Block_byref_a_0 *a = __cself->a; // bound by ref  取出block内部的成员变量a
    __Block_byref_person_1 *person = __cself->person; // bound by ref 取出block内部的成员变量person

    (a->__forwarding->a) = 11; // __forwarding指针:当block在栈上,__forwarding指向栈上block的成员变量a,当block被拷贝到堆上__forwarding指向拷贝到堆上的block的成员变量a,保证block内部一定是访问到堆上的变量。这一步就是通过__forwarding指针找到变量a对其修改。
    
    // 通过__forwarding指针找到变量person对其修改。
    (person->__forwarding->person) = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"));
    
    ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)(person->__forwarding->person), sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_5067e2_mi_1);
    
    // 取值时也是通过__forwarding指针找到变量a、person。
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_5067e2_mi_2,(a->__forwarding->a),((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)(person->__forwarding->person), sel_registerName("name")));
}

代码中关于__forwarding指针指向的说明可参看下图:

forwarding.png

对以上做个总结:用__block修饰auto变量时,编译器会将__block变量包装成对象,对象包含该auto变量且有个__forwarding指针指向包装后的对象,而block内部会持有这个对象;在block内部访问auto变量实际上是通过取得block内部持有的包装后的对象,然后通过这个对象中的__forwarding指针找到包装后的对象或包装后被复制到堆上的对象,最后取出对象中包含的变量进行取值或修改;这就是为什么用__block修饰之后可以修改变量的原因。

__block的内存管理

当block在栈上时,不会对__block变量产生强引用。当block被拷贝到堆上时会调用block内部的copy函数,copy函数调用内部的_Block_object_assign函数根据所指向对象的修饰符对__block变量形成强引用(retain)或弱引用(注意:ARC会retain,MRC时不会retain);block被拷贝到堆上后,其内部用到的成员也都会被拷贝到堆上。当两个栈上的block内部访问同一个block变量,两个block被拷贝到堆上后,堆上只有会有一份__block变量的拷贝,这两个block仍同时持有这个变量;参考下图:

__block_copy.png

通过代码也验证下:

- (void)modifyVariable {
    __block int a = 10;
    
    __block Person *person = [Person new];
    person.name = @"mm";
    
    BlockDemo block = ^{
        a = 11;
        NSLog(@"%d---%@",a,person.name);
    };
    BlockDemo block1 = ^{
        a = 12;
        NSLog(@"%d",a);
    };
    struct __ViewController__modifyVariable_block_impl_0* blockImpl = (__bridge struct __ViewController__modifyVariable_block_impl_0*)block;
    struct __ViewController__modifyVariable_block_impl_0* blockImpl1 = (__bridge struct __ViewController__modifyVariable_block_impl_0*)block1;
    NSLog(@"%p -- %p",blockImpl->a,blockImpl1->a);// 打印结果:0x600000438600 -- 0x600000438600 
    block();
    block1();
}

// 以下是block转换成c++后的代码,我们通过
struct __ViewController__modifyVariable_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 __Block_byref_a_0 {
    void *__isa;
    struct __Block_byref_a_0 *__forwarding;
    int __flags;
    int __size;
    int a;
};

struct __ViewController__modifyVariable_block_impl_0 {
    struct __block_impl impl;
    struct __ViewController__modifyVariable_block_desc_0* Desc;
    struct __Block_byref_a_0 *a; // by ref
    
};

__ViewController__modifyVariable_block_impl_0是block的底层实现,我们通过__ViewController__modifyVariable_block_impl_0来访问block内部持有的__block变量a,代码中可以看到这两个block内部持有的a的内存地址相同的。

当block从堆中移除时,会调用block内部的dispose函数,dispose函数内部调用_Block_object_dispose函数,_Block_object_dispose函数自动释放__block变量。过程如下图:

dispose.png
上一篇 下一篇

猜你喜欢

热点阅读