前面的内容都是使用Block截取的自动变量, 如果我们的Block截取的是对�象, 其实情况基本一样, 除了copy和dispose略有区别.
int main(int argc, const char * argv[]) {
NSMutableArray *array = [NSMutableArray array];
Block block = ^(id obj){
[array addObject:obj];
}; // 只要赋值以后, block 就从栈上 -> copy 到堆上
struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block;
block([NSObject new]);
block([NSObject new]);
block([NSObject new]);
return 0;
}
//2018-11-06 17:11:28.350466+0800 Block-Demo2[51066:2273557] array count = 1
//2018-11-06 17:11:28.350600+0800 Block-Demo2[51066:2273557] array count = 2
//2018-11-06 17:11:28.350719+0800 Block-Demo2[51066:2273557] array count = 3
在{}
结束时,array的变量作用域结束, 变量array被废弃, 其强引用失效, 但是代码运行成功, 因此复制给变量array的NSMutableArray类的对象在该源代码最后Block执行部分, 超出了其作用域而存在.
通过编译器转化以后:
typedef void (^Block)(id obj);
//1. __block_impl 结构体定义, 有以下成员变量: `isa`, `Flags`, `Reserved`, `FuncPtr`
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id __strong array; // 被截获的自动变量, 注意是`id __strong`类型的成员变量, 由于Block在栈/堆上都知道合适废弃内存, 因此这里使用__strong修饰, 同时配合copy/dispose方法去销毁`id __strong`成员
// 构造函数直接截获 array
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id __string _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) {
// Block底层实际使用的成员变量, 将cself强引用的array赋值给局部变量array
id __strong array = __cself->array;
[array addObject:obj];
NSLog(@"array count = %ld", [array count]);
}
// `_Block_object_assign`相当于调用retain实例方法的函数,将对象赋值在对象类型的结构体成员变量中.
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_FILED_IS_OBJECT);
}
// 用来释放赋值在对象类型的结构体成员变量中的对象
static void __main_block_dispose_0(struct __main_block_impl_0 *src) {
_Block_object_dispose(src->array, BLOCK_FILED_IS_OBJECT);
}
//3. __main_block_desc_0, 用于描述特定Block的大小信息. 最重要的是成员使用的copy和dispose函数指针
static struct __main_block_desc_0 {
unsigned long reserved;
unsigned long Block_size;
void (*copy)(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src);
void (*dispose)(struct __main_block_impl_0 *src);
} __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0),
__main_block_copy_0, // id __strong array 成员变量使用的copy函数
__main_block_dispose_0, // id __strong array 成员变量使用的dispose函数
};
int main(int argc, const char * argv[]) {
Block block;
{
id __strong array = [[NSMutableArray alloc] init]; // 默认使用`__strong` 修饰符
block = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA,array, 0x22000000);
block = [Block copy];
}
(*blk->impl.FuncPtr)(block, [NSObject new]);
(*blk->impl.FuncPtr)(block, [NSObject new]);
(*blk->impl.FuncPtr)(block, [NSObject new]);
return 0;
}
我们注意到id __strong array
使用__strong
修饰符, 由于编译器是不知道C语言结构体的初始化和废弃操作,不能很好的管理内存.但是OC的runtime
能够准确的把握Block从栈上copy到堆上以及堆上的Block被废弃的时机, 因此Block用结构体中即使含有__strong
修饰符或__weak
修饰符的变量,也可以恰当的进行初始化和废弃, 这就是在__main_block_desc_0
中引入copy
和dispose
指针, 并且引入__main_block_copy_0
和__main_block_dispose_0
两个静态函数的原因, 简单来说用来管理 在Block结构体(主要是堆上的Block)中的id __strong array
这个成员变量的内存管理操作, 这两个函数会在以下情况下调用:
- copy函数 -- 栈上的Block被复制到堆上时调用
- dispose函数 -- 堆上Block被废弃时调用
那么具体情况什么情况下,Block会被从栈复制到堆呢(也就是以下情况会调用_Block_copy函数):
- 调用Block的copy实例方法时
- Block作为函数的函数返回值返回时
- 将Block赋值给附有__strong修饰符id类型的类或者Block类型成员变量时
- 在方法名中含有
usingBlock
的Cocoa方法或者GCD的API中传递Block时
实际我们在后文看到, 在栈的Block内部捕获了对象, 会导致对象的引用计数增加, 但是并不会强引用该对象. 在堆上的Block, 不仅会导致对象的引用计数增加, 也会强引用该对象.
下面的代码可以输出, array
在Block调用copy以后引用计数是3;
{
id array = [[NSMutableArray alloc] init];
blk = [^(id obj){
[array addObject:obj];
NSLog(@"array count = %d", [array count]);
} copy];
NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)array));
// 输出的 Retain count is 3
}
通过上面的内存状态图和代码, 我们可以看到通过使用__strong修饰符
修饰的自动变量,Block中截获的对象就能超出作用域而存在, 这里同使用__block变量
的逻辑是类似的,唯一不同是copy和dispose中传入的最后的类型参数不一致, 用该参数区分传入的是对象还是__block变量
:
- 对象 --
BLOCK_FILED_IS_OBJECT
- __block变量 --
BLOCK_FILED_IS_BYREF
总结:
-
一旦block中捕获的变量为
对象类型
,block
结构体中的__main_block_desc_0
会出现两个函数copy
和dispose
. 因为block
内部会访问这个对象, block需要拥有这个对象,就需要对被捕获的对象进行强引用, 因此Block内部也对内存进行管理操作.因此一旦block捕捉到了变量的类型是对象类型, 就会生成copy
和dispose
来对内部引用的对象进行内存管理. -
如果block被拷贝到堆上,
copy
函数会调用_Block_object_assign
, 该函数会根据auto
变量的修饰符(__strong
,__weak
,unsafe_unretained
)来做出相应的操作, 行成强引用或者弱引用. -
如果block从堆中移除,
dispose
函数会调用_Block_object_dispose
函数,自动释放引用的auto变量。
如果block捕获的是
__block 基础变量
, 那么block会将它当做一个对象类型
--__Block_byref_xxx_0
来看, 因为它也有copy
和dispose
参考资料
- <<Objective-C 高级编程: iOS与OSX多线程和内存管理>>
- https://blog.csdn.net/deft_mkjing/article/details/53149629
- iOS底层原理总结 - 探寻block的本质(二)
网友评论