《Objective-C高级编程》Blocks 阅读笔记系列
《Objective-C高级编程》Blocks 阅读笔记 item1(Blocks概要和模式)
《Objective-C高级编程》Blocks 阅读笔记 item2(Block的实质)
《Objective-C高级编程》Blocks 阅读笔记 item3(截获自动变量值)
《Objective-C高级编程》Blocks 阅读笔记 item4(__block说明符)
《Objective-C高级编程》Blocks 阅读笔记 item5(Block存储域)
《Objective-C高级编程》Blocks 阅读笔记 item6(__block变量存储域)
《Objective-C高级编程》Blocks 阅读笔记 item7(截获对象)
《Objective-C高级编程》Blocks 阅读笔记 item8(__block变量和对象)
《Objective-C高级编程》Blocks 阅读笔记 item9(Block循环引用)
《Objective-C高级编程》Blocks 阅读笔记 item10(copy/release实例方法)
2.3 Blocks的实现
2.3.6 截获对象
{
id array = [[NSMutableArray alloc] init];
}
该源代码生成并持有NSMutableArray类的对象,但是附有__strong修饰符的赋值目标(变量array)变量作用域立即就会结束,因此对象被立即释放并废弃。
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
blk = [^(id obj){
[array addObject:obj];
NSLog(@"array count = %ld", [array count]);
} copy]; // 调用copy方法(Block从栈复制到堆)
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
变量作用域结束的同时,变量array被废弃,其对NSMutableArray类的对象的强引用失效,因此NSMutableArray类的对象被释放并废弃(此处我不确定是否会被废弃)。但是,该源代码运行正常,执行结果如下:
array count = 1
array count = 2
array count = 3
这意味着赋值给变量array的NSMutableArray类的对象在Block的执行部分超出其变量作用域而存在。
经clang转换:
/* Block的结构体 / 函数部分 */
// 结构体 __main_block_impl_0
struct __main_block_impl_0 {
// 成员变量
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id __strong array;
/*
理解:
1. 被NSMutableArray类对象并被截获的自动变量array,是附有__strong修饰符的成员变量。在Objective-C中,C语言结构体不能含有附有__strong修饰符的变量。因为编译器不知道何时进行C语言结构体的初始化和废弃操作,不能很好地管理内存。
2. 但是,Objective-C的运行时库能准确把握Block从栈复制到堆以及堆上的Block被废弃的时机,因此Block的结构体即时含有附有__stong修饰符或__weak修饰符的变量,也可以恰当地进行初始化和废弃。
*/
// 构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id __strong_array, int flags =0) : array(_array){
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 静态函数 __main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj)
{
id __strong array = __cself->array;
[array addObject:obj];
NSLog(@"array count = %ld", [array count]);
}
// 静态函数 __main_block_copy_0
static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src){
_Block_object_assign(&dst->array, src->array, BLOCK_FIELD_IS_OBJECT);
/*
理解:
1. __main_block_copy_0函数使用_Block_object_assign函数将“对象类型对象”赋值给Block的结构体成员变量array中并持有该对象
2. _Block_object_assign函数调用“相当于ratain实例方法的函数”,将“对象”赋值在对象类型的结构体成员变量中。
*/
}
// 静态函数 __main_block_dispose_0
static void __main_block_dispose_0(struct __main_block_impl_0 *src){
_Block_object_dispose(src->array, BLOCK_FIELD_IS_OBJECT);
/*
理解:
1. __main_block_dispose_0函数使用_Block_object_dispose函数,释放赋值在Block的结构体成员变量array中的对象。
2. _Block_object_dispose函数调用相当于release实例方法的函数,释放赋值在对象类型的结构体成员变量中的对象。
*/
}
// 静态结构体 __main_block_desc_0
static struct __main_block_desc_0{
unsigned long reserved;
unsigned long Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __mian_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
};
/*
理解:
1. __main_block_copy_0函数(copy函数)和__main_block_dispose_0函数(dispose函数)指针被赋值__main_block_desc_0结构体成员变量copy和dispose中,但是在转换后的源代码中,这些函数包括使用指针全都没有被调用。
2. 而是,在Block从栈复制到堆时以及堆上的Block被废弃时会调用这些函数。
*/
/* Block语法,使用Block部分 */
blk_t blk;
{
id __strong array = [[NSMutableArray alloc] init];
blk = &__main_block_impl_0(__main_block_func_0, &__mian_block_desc_0_DATA, array, 0x22000000);
blk = [blk copy];
}
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
表 调用copy函数和dispose函数的时机
函数 | 调用时机 |
---|---|
copy函数 | 栈上的Block复制到堆时 |
dispose函数 | 堆上的Block被废弃时 |
*** 何时栈上的Block会复制到堆 ***
- 调用Block的copy实例方法时
- Block作为函数返回值返回时
- 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
- 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时
在栈上的Block被复制到堆时,copy函数被调用,而在释放复制到堆上的Block后,谁都不持有Block而被废弃时,dispose函数被调用。正因为这种构造,通过使用附有__strong修饰符的自动变量,Block中截获的对象才能给超出其变量作用域而存在。
*** 如何区分copy函数和dispose函数的对象类型 ***
表 截获对象时和使用__block变量时的不同
对象 | BLOCK_FIELD_IS_OBJECT |
---|---|
__block变量 | BLOCK_FIELD_IS_BYREF |
通过BLOCK_FIELD_IS_OBJECT和BLOCK_FIELD_IS_BYREF参数,区分copy函数和dispose函数的对象类型是对象还是__block变量。
但是,与copy函数持有被截获的对象,dispose函数释放截获的对象相同,copy函数持有所使用的__block变量,dispose函数释放所使用的__block。
由此可知,Block中使用的赋值给附有__stong修饰符的自动变量的对象和复制到堆上的__block变量,由于被堆上的Block所持有,因而可超出其变量作用域而存在。
*** 何种情形下,不调用Block的copy实例方法 ***
在Block使用对象类型自动变量是,除以下情形外,推荐调用Block的copy实例方法:
- Block作为函数返回值返回时
- 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
- 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时