目录
Block的类型
Block的本质是OC对象,那么我们通过下面代码看下它的类及继承关系
void(^blk)(void)= ^(void){
};
NSLog(@"[blk class]= %@",[blk class]);
NSLog(@"[[blk class] superclass]= %@",[[blk class] superclass]);
NSLog(@"[[[blk class] superclass] superclass]= %@",[[[blk class] superclass] superclass]);
NSLog(@"[[[[blk class] superclass] superclass] superclass]= %@",[[[[blk class] superclass] superclass] superclass]);
打印日志如下
[blk class]= __NSGlobalBlock__
[[blk class] superclass]= __NSGlobalBlock
[[[blk class] superclass] superclass]= NSBlock
[[[[blk class] superclass] superclass] superclass]= NSObject
可知继承关系为:__NSGlobalBlock:__NSGlobalBlock:NSBlock:NSObject
通过代码来看下Block的类型有几种,为了更精确的反应实际情况,我们暂时把arc关掉。
int weight = 80;
int main(int argc, char * argv[]) {
@autoreleasepool {
//1.没有访问auto变量: __NSGlobalBlock__
void(^blk00)(void)= ^(void){
};
NSLog(@"[blk00 class]= %@",[blk00 class]);
//2.访问全局变量,没有访问auto变量: __NSGlobalBlock__
void(^blk01)(void)= ^(void){
NSLog(@"weight=%d",weight);
};
NSLog(@"[blk01 class]= %@",[blk01 class]);
//3.访问静态变量,没有访问auto变量: __NSGlobalBlock__
static int height = 180;
void(^blk02)(void)= ^(void){
NSLog(@"height=%d",height);
};
NSLog(@"[blk02 class]= %@",[blk02 class]);
//4.访问auto变量: __NSStackBlock__
int age = 10;
void(^blk1)(void)= ^(void){
NSLog(@"age=%d",age);
};
NSLog(@"[blk1 class]= %@",[blk1 class]);
//5.访问auto变量,并执行了copy操作: __NSMallocBlock__
NSLog(@"[blk2 class]= %@",[[^(void){NSLog(@"age=%d",age);} copy] class]);
}
return 0;
}
打印日志如下:
[blk00 class]= __NSGlobalBlock__
[blk01 class]= __NSGlobalBlock__
[blk02 class]= __NSGlobalBlock__
[blk1 class]= __NSStackBlock__
[blk2 class]= __NSMallocBlock__
没有捕获auto变量:__NSGlobalBlock__
捕获auto变量:__NSStackBlock__
__NSStackBlock__
执行copy后: __NSMallocBlock__
Block的内存分配图
每一种类型的Block调用copy后的结果如下图所示:
Block的Copy
一、在ARC环境下,Block作为函数返回值,会默认执行copy操作。
看下代码
typedef void(^blk)(void);
//返回值为block,且访问了auto变量,所以类型为 __NSStackBlock__;
blk test(void){
int age = 10;
return ^{
NSLog(@"age = %d",age);
};
}
int main(int argc, char * argv[]) {
@autoreleasepool {
blk b = test();
//b位于栈空间,当test的函数调用结束后,应当销毁但实际上还可以调用。
b();
//打印类型为__NSMallocBlock__ 证明进行了copy操作,复制到了堆上
NSLog(@"[b class] = %@",[b class]);
}
return 0;
}
打印日志如下:
age = 10
[b class] = __NSMallocBlock__
二、在ARC环境下,有强指针指向Block,会默认执行copy操作。
看下代码
typedef void(^blk)(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
int age = 10;
blk b = ^{
NSLog(@"age = %d",age);
};
NSLog(@"[b class] = %@",[b class]);
}
return 0;
}
打印日志
[b class] = __NSMallocBlock__
我们对代码做下改动再来看下
typedef void(^blk)(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
int age = 10;
NSLog(@"[b class] = %@",[^{
NSLog(@"age = %d",age);
} class]);
}
return 0;
}
打印日志
[b class] = __NSStackBlock__
可以看到有强指针指向的Block类型为__NSMallocBlock__
,而没有强指针指向的为__NSStackBlock__
。
三、Block作为Cocoa API中方法名含有usingBlock的方法参数时,会默认执行copy操作。
int age = 10;
NSArray * array = [NSArray new];
//该block参数位于堆空间
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"age = %d",age);
}];
四、Block作为GCD API方法参数时,会默认执行copy操作。
int age = 10;
//该block参数,位于堆空间
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"age = %d",age);
});
以上举例中的Block执行copy操作后,全部位于内存的堆空间
Block访问对象类型的auto变量
上面分析可知,访问了auto变量的Block类型为__NSStackBlock__
。对__NSStackBlock__
类型的Block执行copy操作后类型变为__NSMallocBlock__
。那我们从这两种类型开始分析。
一、NSStackBlock 与 对象类型的auto变量
typedef void(^blk)(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
__weak blk b = nil;
{
Person * person = [[Person alloc] init];
person.age = 10;
b=^(){
NSLog(@"person.age = %d",person.age);
};
}
NSLog(@"----- %@",[b class]);
}
return 0;
}
日志打印
-[Person dealloc]
----- __NSStackBlock__
在打印Block类型之前,Person对象释放了,__NSStackBlock__
类型的Block不会对对象类型auto变量产生强引用。不管变量是__weak修饰还是__strong修饰。
二、NSMallocBlock 与 对象类型的auto变量
typedef void(^blk)(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
blk b = nil;
{
Person * person = [[Person alloc] init];
person.age = 10;
b=^(){
NSLog(@"person.age = %d",person.age);
};
}
NSLog(@"----- %@",[b class]);
}
return 0;
}
日志打印
----- __NSMallocBlock__
-[Person dealloc]
Person对象在离开它的作用域时并没有释放,而是在Block对象b离开它的作用域销毁时才释放的。
对代码做下改动试下,将Person对象的修饰符改为__weak;
typedef void(^blk)(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
blk b = nil;
{
__weak Person * person = [[Person alloc] init];
person.age = 10;
b=^(){
NSLog(@"person.age = %d",person.age);
};
}
NSLog(@"----- %@",[b class]);
}
return 0;
}
日志打印
-[Person dealloc]
----- __NSMallocBlock__
Person对象在离开它的作用域时释放了,证明Block没有对齐产生强引用。
三、结论
1、如果Block在栈上,不会对对象类型auto变量产生强引用。
2、如果Block在堆上,会根据对象类型auto变量的修饰符 __strong、 __weak、 __unsafe_unretained做出相应的操作,形成强引用或若引用。
四、分析
源码如下:
typedef void(^blk)(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
Person * person = [[Person alloc] init];
person.age = 10;
blk b=^(){
NSLog(@"person.age = %d",person.age);
};
}
return 0;
}
执行指令并精简代码:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m
typedef void(*blk)(void);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__strong person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Person *__strong person = __cself->person;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_s1_dbzn9w0x6h121vpvtg1vfcf80000gn_T_main_292be7_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->person, (void*)src->person, 3);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->person, 3);
}
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[]) {
Person * person = objc_msgSend(
objc_msgSend(objc_getClass("Person"), sel_registerName("alloc")),
sel_registerName("init")
);
objc_msgSend(person, sel_registerName("setAge:"), 10);
blk b=&__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
person,
570425344
);
}
return 0;
}
注意到在结构体__main_block_impl_0内对变量person的捕获是Person *__strong person;
。
另外在结构体__main_block_desc_0_DATA内还多出了两个参数,这两个参数是函数指针分别指向__main_block_copy_0和__main_block_dispose_0。
1、Block被拷贝到堆上会调用Block内部的copy函数,copy函数内部会调用__main_block_copy_函数,__main_block_copy_内部的_Block_object_assign函数会根据auto变量的修饰符 __strong、 __weak、 __unsafe_unretained做出相应的操作,形成强引用或若引用。
2、Block从堆上移除会调用Block内部的dispose函数,dispose函数内部会调用__main_block_dispose_0函数,__main_block_dispose_0内部的_Block_object_dispose函数会自动释放引用auto变量。
我们在看下__weak 修饰Person对象时,生成的C++源码。
源码
typedef void(^blk)(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
__weak Person * person = [[Person alloc] init];
person.age = 10;
blk b=^(){
NSLog(@"person.age = %d",person.age);
};
}
return 0;
}
执行指令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__weak person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看到 Person *__weak person;
修饰符变为了__weak,所以__NSMallocBlock__类型的Block内部对__weak修饰的Person对象不会产生强引用。
行者常至,为者常成!