美文网首页Ios@IONIC精通blockiOS
__block的实现和内存管理

__block的实现和内存管理

作者: YY_Lee | 来源:发表于2019-01-30 17:28 被阅读1次

    __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

    相关文章

      网友评论

        本文标题:__block的实现和内存管理

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