《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;
}
- 注意,这里相比较普通的__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。