美文网首页
Block 原理面试(2)

Block 原理面试(2)

作者: FY_Chao | 来源:发表于2020-02-28 22:47 被阅读0次

    拾:Block 原理面试(2)

    • __block的作用是什么?有什么使用注意点?

    答:__block可以用于解决 block 内部无法修改auto变量值,__block不能也没必要修饰全局变量、静态变量(static)详见下文。

    __block 的原理

        // 编译报错
        int  age = 10;
        void(^block)(void) = ^ {
            age = 20;
        };
    
    

    上述代码在 Xcode 中编译的时候就会报错,block 是无法直接修改外部 auto 变量。在不添加 __weak的情况下,static 的变量和全局变量也可以直接在 block 做出修改。
    是因为 static 变量传递给 block 的是变量的地址,全局变量则是一直存在内存中,block 都可以访问到外部变量并修改(详见玖:Block 面试(1)-值捕获)。

    __block作用

        __block int  age = 10;
        __block NSObject *objc = [[NSObject alloc] init];
        
        void(^block)(void) = ^ {
            objc = nil;
            age = 20;
        };
    

    通过 clang 命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 main.m编译后:

    [图片上传失败...(image-83f7d2-1582901241368)]

    简化代码后:
    [图片上传失败...(image-f01a26-1582901241368)]

    struct __Block_byref_age_0 {
      void *__isa;
    __Block_byref_age_0 *__forwarding;
     int __flags;
     int __size;
     int age;
    };
    struct __Block_byref_objc_1 {
      void *__isa;
    __Block_byref_objc_1 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     NSObject *__strong objc;
    };
    

    可以发现添加__block之后的变量都转换成了 __Block_byref_age_0__Block_byref_objc_1的结构体的类型,且对象生成的__Block_byref_objc_1会比基本数据类型的结构体多两个内存管理的函数指针(__Block_byref_id_object_copy__Block_byref_id_object_dispose):

    /**
        __block 外部变量编译生成的 struct 对外部变量内存管理的两个方法
        不要和 block 对变量生成的结构体的内存管理方法搞混。
    */
    /// __block外部变量生成的 struct 对外部变量内存管理
    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);
    }
    
    /// block 对变量生成的结构体内存管理的两个方法
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->objc, (void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    

    __block 修饰的变量编译后的结构体地址传递给 block 被强引用着,而每个__block结构体内部又有一个引用着外部变量的成员(如上文中__Block_byref_age_0的 age,__Block_byref_objc_1的objc),对外部变量的引用是 strong还是 weak 则依赖值捕获的外部变量自身的修饰属性。

    [图片上传失败...(image-468cb4-1582901241368)]

    另外__block 修饰的变量编译后的结构体包含一个__forwarding指向结构体本身的指针。添加 __block 之后的变量无论是在 block 内部还是外部都会通过__forwarding指针作为中间者来访问、修改变量的值,如:age.__forwarding->age = 30; __forwarding 指针主要功能是用来确保无论从 block 外部还是内部都能够访问到正确的变量地址。

    __forwarding 指针

    前面提到过 __forwarding 指针主要功能是用来确保能够访问到正确的变量地址,那么它是如何确保的呢?

    以上文为例__block int age = 10;在未被 block copy到堆上的时候__forwarding 指针指向的是栈上的结构体:

    [图片上传失败...(image-9b4db6-1582901241368)]

    当 block 被 copy 到堆上的时候,栈上变量的__forwarding 被指向了堆中的结构体地址,以后无论从栈上还是堆上访问结构体都会访问到堆:

    [图片上传失败...(image-493ea1-1582901241368)]

    Block对变量的内存管理总结

    • block 在栈上的时候,不会强引用外部的任何变量
    • block 从栈到堆上的时候,会有一次 copy 操作,在 copy 操作的时__main_block_copy_0函数会根据捕获外部变量的 strongweak修饰,来直接对强/弱引用外部变量。
    • 如果捕获变量是 __block 修饰的,copy 到堆上的 block 会将变量转换成的结构体 copy 到堆上同时生成强引用,变量转换成的结构体自身对外部变量的强弱引用则是根据捕获变量时变量自身的强弱修饰符决定。
    • 如果堆上的 block 被销毁了,__main_block_dispose_0会对 block 引用的变量进行 release 操作。

    文章首发:由面试题来了解iOS底层原理

    相关文章

      网友评论

          本文标题:Block 原理面试(2)

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