美文网首页
《Objective-C高级编程 iOS与OS X多线程与内存管

《Objective-C高级编程 iOS与OS X多线程与内存管

作者: 我才是臭吉吉 | 来源:发表于2019-01-14 18:41 被阅读4次

    Blocks篇:6.Blocks捕获的OC对象及其内存管理

    之前所说的,都是Block对象捕获基本数据类型变量时的处理方式。现在我们看一下对于OC对象被Block捕获时的情况。

    1.Block对象捕获OC对象

    // main.m
    
    typedef void (^MyBlock)(id obj);
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            MyBlock myBlock;
            
            {
                // 声明数组作为被捕获变量
                NSMutableArray *array = [[NSMutableArray alloc] init];
            
                // 声明block
                myBlock = ^(id obj){
                    // 向捕获的数组中添加对象
                    [array addObject:obj];
                    printf("个数:%ld\n", array.count);
                };
            }
            
            // 执行block
            myBlock([NSObject new]);
            myBlock([NSObject new]);
            myBlock([NSObject new]);
            
        }
        return 0;
    }
    

    此代码运行正常无误。这也直接证明了Block对象对该数组对象进行了强引用,使被捕获对象的生存周期超过了原始作用域。究其原因,查看部分转换后的代码:

    // main.cpp
    
    /** Block的完整结构体 */
    struct __main_block_impl_0 {
        struct __block_impl impl;
        struct __main_block_desc_0* Desc;
        // 声明捕获的OC对象(ARC下为__strong修饰,强引用)
        NSMutableArray *array;
        __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, int flags=0) : array(_array) {
            impl.isa = &_NSConcreteStackBlock;
            impl.Flags = flags;
            impl.FuncPtr = fp;
            Desc = desc;
        }
    };
    
    
    /** Block的执行函数 */
    static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
        NSMutableArray *array = __cself->array; // bound by copy
        
        ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
    
        printf("个数:%ld\n", ((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
    }
    
    /** Block复制到堆时附带执行的函数 */
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
        // _Block_object_assign相当于retain;BLOCK_FIELD_IS_OBJECT标明捕获对象为OC对象
        _Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    
    /** Block复制从堆中释放时附带执行的函数 */
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {
        // _Block_object_dispose相当于release;BLOCK_FIELD_IS_OBJECT标明捕获对象为OC对象
        _Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    
    /** Block描述信息结构体 */
    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};
    

    在转换代码中显而易见,Block将捕获的OC对象以强引用的方式(ARC下隐含为__strong),作为结构体的成员变量。并在Block的描述信息对象中提供了copy和dispose方法,在适当时机调用以配合内存管理。

    注意:

    • 由于编译器支持Block结构的类引用计数的内存管理方式,即ARC下,系统可以在适当时机分配和释放Block对象的内存。故在Block的相关结构体中可以直接存入带有所有权修饰符的OC对象。
    • 由于OC对象本身分配在堆内存中,Block对象只需通过指针传递即可完成捕获
    • 由于捕获后,Block对象也参与了OC对象的内存管理,故需要提供保留和释放相关函数,以备系统在堆内存中创建(copy操作)和释放Block对象(dispose操作)时进行调用。这里则与__block变量时一样,使用了_Block_object_assign_Block_object_dispose函数对OC对象进行保留和释放操作。

    补充:

    _Block_object_assign以及_Block_object_dispose中,用于区分捕获变量类型的标识:

    捕获的对象 标识值
    OC对象 BLOCK_FIELD_IS_OBJECT
    __block修饰的变量或对象 BLOCK_FIELD_IS_BYREF

    2.__block修饰的OC对象

    2.1 __block修饰的强引用对象(__strong所有权)

    还是先看实例:

    // main.m
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // 声明__block修饰的OC对象
            __block NSObject *myObj = [[NSObject alloc] init];
            
            
            void (^myBlock)(void) = ^{
                // 修改捕获对象
                myObj = [[NSArray alloc] init];
            };
            
            // 执行Block
            myBlock();
            
            printf("%p\n", (__bridge void *)myObj);
            
            // 修改__block对象
            myObj = [[NSObject alloc] init];
            
            printf("%p\n", (__bridge void *)myObj);
    
        }
        return 0;
    }
    
    

    可以看到,当Block执行后,在外部读取捕获的OC对象,可以发现仍然指向同一地址。这与__block修饰基本变量的情况一致,也是需要通过生成特定捕获变量的结构体实例,在拷贝到堆内存后,最终访问均指向相同的对象。

    // main.cpp
    
    /** 捕获对象的结构体 */
    struct __Block_byref_myObj_0 {
        void *__isa;
        /** 指向自身结构体实例的指针 */
        __Block_byref_myObj_0 *__forwarding;
        int __flags;
        int __size;
        /** 分配到堆内存时的执行函数 */
        void (*__Block_byref_id_object_copy)(void*, void*);
        /** 在堆内存中释放时的执行函数 */
        void (*__Block_byref_id_object_dispose)(void*);
        /** 真正的值(OC对象),在ARC下默认为__strong修饰 */
        NSObject *myObj;
    };
    
    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);
    }
    
    struct __main_block_impl_0 {
        struct __block_impl impl;
        struct __main_block_desc_0* Desc;
        /** 捕获对象的结构体实例 */
        __Block_byref_myObj_0 *myObj; // by ref
        /** 构造函数,其中捕获变量传递的是__forwarding指向的对象 */
        __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_myObj_0 *_myObj, int flags=0) : myObj(_myObj->__forwarding) {
            impl.isa = &_NSConcreteStackBlock;
            impl.Flags = flags;
            impl.FuncPtr = fp;
            Desc = desc;
        }
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        
        __Block_byref_myObj_0 *myObj = __cself->myObj; // bound by ref
        
        // 对堆中的__block对象(__forwarding实例中的OC对象)进行修改
        (myObj->__forwarding->myObj) = ((NSArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("alloc")), sel_registerName("init"));
    }
    
    /** 在堆内存上生成时的回调函数 */
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
        // 对捕获生成的__block对象进行赋值并保留
        _Block_object_assign((void*)&dst->myObj, (void*)src->myObj, 8/*BLOCK_FIELD_IS_BYREF*/);
    }
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {
        // 对捕获生成的__block对象进行释放
        _Block_object_dispose((void*)src->myObj, 8/*BLOCK_FIELD_IS_BYREF*/);
    }
    
    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
    };
    
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ 
        { 
            __AtAutoreleasePool __autoreleasepool; 
            
            // 生成__block修饰的对象
            __attribute__((__blocks__(byref))) __Block_byref_myObj_0 myObj = {
                (void*)0,
                (__Block_byref_myObj_0 *)&myObj, 
                33554432, 
                sizeof(__Block_byref_myObj_0),
                // 赋值OC对象的内存保留函数
                __Block_byref_id_object_copy_131, 
                // 赋值OC对象的内存释放函数
                __Block_byref_id_object_dispose_131, 
                // 创建真正的OC对象
                ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))
            };
            
            // 声明并创建Block对象
            void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_myObj_0 *)&myObj, 570425344));
            
            // 调用Block并传入本身指针
            ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
            
            // 访问栈上的__block对象
            printf("%p\n", (__bridge void *)(myObj.__forwarding->myObj));
            
            // 修改栈上的__block对象
            (myObj.__forwarding->myObj) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
            
            // 访问栈上的__block对象
            printf("%p\n", (__bridge void *)(myObj.__forwarding->myObj));
    
        }
        return 0;
    }
    
    • 注意,这里相比较普通的__block变量时,在__block变量结构体中,新增了对于__block变量在堆内存中进行分配和释放时的函数指针成员
    /** 分配到堆内存时的执行函数 */
    void (*__Block_byref_id_object_copy)(void*, void*);
    /** 在堆内存中释放时的执行函数 */
    void (*__Block_byref_id_object_dispose)(void*);
    

    这是由于__block修饰的是OC对象,其结构体实例赋值此OC对象的内存管理,故需要提供保留和释放操作。

    • 同样的,在栈中(声明__block对象所在的地方)或是堆中(Block对象执行的函数体中),都可以通过__forwarding指针访问到相同的OC对象
    // 获取堆内存中的__block对象【栈__block对象的__forwarding依然指向的是堆中的__block对象】
    struct __Block_byref_myObj_0 __blockVar_onHeap = myObj.__forwarding;
    // 得到真正的OC对象
    NSObject realObj = __blockVar_onStack->myObj;
    

    2.2 __block修饰的其他OC对象

    • 当__block修饰的OC对象为弱对象时,我们以最开始的例子进行修改验证:
    // main.m
    
    typedef void (^MyBlock)(id obj);
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            MyBlock myBlock;
    
            {
                // 声明数组
                NSMutableArray *array = [[NSMutableArray alloc] init];
                
                // 将待捕获对象声明为弱对象
                __block __weak NSMutableArray *weakArray = array;
    
                // 声明block
                myBlock = ^(id obj){
                    [weakArray addObject:obj];
    
                    printf("个数:%ld\n", weakArray.count);
                };
            }
    
            // 执行block(向数组中添加对象)
            myBlock([NSObject new]);
            myBlock([NSObject new]);
            myBlock([NSObject new]);
        }
        return 0;
    }
    

    执行情况为:

    个数:0
    个数:0
    个数:0
    Program ended with exit code: 0

    在__block对象中保留的是数组的弱引用,当原数组对象释放后,__block对象中的OC对象自动置为nil,故结果为0。

    • 当__block修饰的OC对象为__unsafe_unretained修饰的对象时,与弱引用情况相同,只是需要保证不要访问野指针。
    • __block修饰的OC对象不允许标记为__autoreleasing。

    相关文章

      网友评论

          本文标题:《Objective-C高级编程 iOS与OS X多线程与内存管

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