block的类型
block
有 3
种类型,可以通过调用 class
方法或者 isa
指针查看具体类型。
3
种 类型的 block
都是继承自 NSBlock
,NSBlock
又继承自 NSObject
。
MRC
环境下 block
的类型和复制效果
类型 | 环境 | 内存区域 | 复制效果 |
---|---|---|---|
__NSStackBlock__ (_NSConcreteStackBlock ) |
访问了auto 变量 |
栈 | 从栈复制到堆 |
__NSMallocBlock__ (_NSConcreteMallocBlock ) |
__NSStackBlock__ 调用了copy |
堆 | 引用计数增加 |
__NSGlobalBlock__ (_NSConcreteGlobalBlock ) |
没有访问auto 变量 |
程序的数据区域(.data ) |
什么也不做 |
NSStackBlock存在的问题
如果在 MRC
环境,确实是访问了 auto
变量就是 __NSStackBlock__
类型。但是如果在 ARC
环境,变成了 __NSMallocBlock__
。
这是因为 ARC
自动做了很多事情,比如自动调用了 copy
方法将 __NSStackBlock__
变为了 __NSMallocBlock__
。
Target -> Build Settings -> Objective-C Automatic Reference Counting
改为NO
则是MRC
,默认是YES
。
下面代码在 MRC
环境下运行,发现在 test
函数执行完毕后再执行 block
,age
变量变成了垃圾数据:
void (^block)(void);
void test() {
int age = 10;
block = ^{
NSLog(@"age = %d", age);
};
NSLog(@"block=%@", [block class]);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test(); // block=__NSStackBlock__
block(); // age = 1876947496
}
return 0;
}
这是因为 block
是存储在栈内存的,函数执行完毕后,数据可能就被系统回收了。为了解决这个问题,就得把 block
放到堆内存中。
稍微改进一下,block
调用 copy
方法:
void (^block)(void);
void test() {
int age = 10;
block = [^{
NSLog(@"age = %d", age);
} copy];
NSLog(@"block=%@", [block class]);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test(); // block=__NSMallocBlock__
block(); // age = 10
}
return 0;
}
可以发现,__NSStackBlock__
调用 copy
后 block
类型变为了 __NSMallocBlock__
。也就是 block
是存储在堆内存中,函数执行完毕后,数据不会被回收,需要自己管理内存释放。
ARC下block的自动copy
在 ARC
环境下,编译器会根据情况自动将栈上的 block
复制到堆上,比如以下情况:
block
作为函数返回值时。- 将
block
赋值给__strong
指针时。 block
作为Cocoa API
中方法名含有usingBlock
的方法参数时。block
作为GCD API
的方法参数时。
typedef void (^Block)(void);
Block test() {
int age = 10;
return ^{
NSLog(@"age = %d", age);
};;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
// 作为函数返回值时
NSLog(@"%@", [test() class]);
// 赋值给__strong指针时,默认就是__strong
Block block = ^{
NSLog(@"age = %d", age);
};
NSLog(@"%@", [block class]);
// 没有赋值给__strong指针,不会copy
NSLog(@"%@", [^{
NSLog(@"age = %d", age);
} class]);
// 作为Cocoa API中方法名含有usingBlock的方法参数时
[@[] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {}];
// 作为GCD API的方法参数时
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{});
}
return 0;
}
MRC
下 block
属性的建议写法:
@property (copy, nonatomic) void (^block)(void);
ARC
下 block
属性的建议写法
@property (copy, nonatomic) void (^block)(void);
@property (strong, nonatomic) void (^block)(void);