截获对象
这一篇我们来看一下Block截获对象
。
{
id array = [[NSMutableArray alloc] init];
}
id类型
的变量相当于默认附有__strong修饰符
,超出作用域
对象会被立即释放
并且废弃
,我们来看一下以下源码。
typedef void(^blk_t)(id);
int main(int argc, char * argv[]) {
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
blk = [^(id obj){
[array addObject:obj];
NSLog(@"Now the count of array = %ld", (long)[array count]);
} copy];
}
blk([[NSString alloc] init]);
blk([[NSString alloc] init]);
blk([[NSString alloc] init]);
}
变量作用域结束的同时,变量array
被废弃,强引用失效,因此赋值给变量array
的NSMutableArray
类的对象必定被释放并废弃
。但是源码运行正常,执行结果如下:
2018-10-28 16:32:55.885619+0800 test[2282:245454] Now the count of array = 1
2018-10-28 16:32:55.887405+0800 test[2282:245454] Now the count of array = 2
2018-10-28 16:32:55.887618+0800 test[2282:245454] Now the count of array = 3
这一结果意味着赋值给变量array
的NSMutableArray
类的对象在该源码最后Block的执行部分超出
其变量作用域
而存在。转换后的源代码如下
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id array;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
id array = __cself->array; // bound by copy
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_f6xspfd14l98cwj9w_ngytvm0000gp_T_main_844042_mi_0, (long)((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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, char * argv[]) {
//使用Block部分
blk_t blk;
{
id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));
}
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
}
我们还是像之前一样分解开来看,先看结构体内的内容。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id array;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
我们可以发现被截获
的自动变量array
变成了Block结构体中附有__strong修饰符
的成员变量
。
其实在Objective -C
中,C语言结构体不能
含有附有__strong修饰符
的变量。因为编译器
不知道应该何时进行C语言结构体的初始化和废弃操作,不能良好的管理内存。但是Objective-C
的运行时库
能够准确的把握Block从栈复制到堆以及堆上的Block被废弃的时机,因此Block结构体中即使含有附有__strong
或者__weak
修饰符的变量,也可以恰当的进行初始化和废弃。
为此需要使用在__main_block_desc_0
结构体中增加的成员变量copy
和dispose
,以及作为指针赋值给该成员变量的__main_block_copy_0
函数和__main_block_dispose_0
函数。
由于在该源代码的Block结构体
中,含有附有__strong
修饰符的对象类型
变量array
,所以需要恰当管理赋值给变量array
的对象。因此__main_block_copy_0
函数使用_Block_object_assign
函数将对象类型
的对象赋值给Block结构体
的成员变量array
中并持有
该对象。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign(
(void*)&dst->array,
(void*)src->array, /*BLOCK_FIELD_IS_OBJECT*/);
}
_Block_object_assign
函数调用相当于retain
实例方法的函数,将对象
赋值在对象类型
的结构体成员变量
中。
另外,__main_block_dispose_0
函数使用_Block_object_dispose
函数,释放
赋值在Block结构体成员变量array
中的对象。
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
_Block_object_dispose
函数调用相当于release
实例方法的函数,释放
赋值在Block结构体成员变量array
中的对象。
虽然此__main_block_copy_0
函数和__main_block_dispose_0
函数指针
被赋值在__main_block_desc_0
结构体成员变量copy
和dispose
中,但转换后的源代码中,这些函数
包括使用指针
全都没有被调用,因为这些只有在Block从栈复制到堆
的时候以及堆上的Block被废弃
时会被调用。
以下为栈
上Block复制
到堆
的几种情况:
1.调用Block的copy
实例方法时。
2.Block作为参数返回值
时。
3.将Block赋值给附有__strong
修饰符id类型
的类或Block类型成员变量
时。
4.在方法名中含有usingBlock
的Cocoa
框架方法或GCD
的API
中传递Block时。
在调用Block的copy
实例方法时,如果Block配置在栈
上,那么该Block会从栈复制到堆
。Block作为函数返回值
时,将Block赋值给附有__strong
修饰符id类型
的类或Block类型成员变量
时,编译器
自动的将对象的Block作为参数
调用_Block_copy
函数,这与调用Block的copy
实例方法的效果相同。在方法名中含有usingBlock
的Cocoa
框架方法或GCD
的API中传递Block时,该方法或函数内部
对传递过来的Block调用Block的copy
实例方法或者_Block_copy
函数。
其实以上的情况都可归结为_Block_copy
函数被调用的时候Block从栈复制到堆
。
相对的,在释放
复制到堆上的Block后,谁都不持有Block而使其被废弃
时调用dispose
函数。相当于对象的dealloc
方法。
有了这种构造,通过使用附有__strong
修饰符的自动变量
,Block中截获的对象就能够超出
作用域而存在。
我们再来回顾一下上面的源码,如果没有copy
方法会怎样?
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
blk = ^(id obj){
[array addObject:obj];
NSLog(@"Now the count of array = %ld", (long)[array count]);
};
}
blk([[NSString alloc] init]);
blk([[NSString alloc] init]);
blk([[NSString alloc] init]);
执行该源码后,程序会强制结束
,只有调用_Block_copy
函数才能持有
截获到的附有__strong
修饰符的对象类型
的自动变量值,所以像上面代码这样不调用_Block_copy
函数的情况下,即使截获了对象,它也会随着变量的作用域结束
而被废弃
。
网友评论