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

2019-01-14  本文已影响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;
}
/** 分配到堆内存时的执行函数 */
void (*__Block_byref_id_object_copy)(void*, void*);
/** 在堆内存中释放时的执行函数 */
void (*__Block_byref_id_object_dispose)(void*);

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

// 获取堆内存中的__block对象【栈__block对象的__forwarding依然指向的是堆中的__block对象】
struct __Block_byref_myObj_0 __blockVar_onHeap = myObj.__forwarding;
// 得到真正的OC对象
NSObject realObj = __blockVar_onStack->myObj;

2.2 __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。

上一篇下一篇

猜你喜欢

热点阅读