__block发挥作用的原理:
将栈上用__block修饰的自动变量封装成一个结构体,让其在堆上创建,以方便从栈上或堆上访问和修改同一份数据。
Block 几种类型通过代码验证:
没有访问 auto变量 的block 就是 __NSGlobalBlock
auto变量:自动变量,离开作用域就会销毁,一般我们创建的局部变量都是auto变量 ,比如 int age = 10,系统会在默认在前面加上auto int age = 10
如下:
int main(int argc, char * argv[]) {
@autoreleasepool {
static int age = 10;
void(^block)(void) = ^{
NSLog(@"---block:Hello, World! %d",age);
};
}
return 0;
}
控制台输出:NSGlobalBlock
访问了auto变量 的block 就是 __NSStackBlock
如下:
int main(int argc, char * argv[]) {
@autoreleasepool {
int age = 10;
void(^block)(void) = ^{
NSLog(@"---block:Hello, World! %d",age);
};
}
return 0;
}
打印:
2022-02-21 14:59:11.430473+0800 Test[35944:1599369] ---block class:__NSMallocBlock__
为什么打印NSMallocBlock,刚才不是说访问了auto变量就是__NSStackBlock吗?
因为这里我们使用的是ARC,在ARC环境下,Xcode编译器再某些情况会默认帮我们做调用copy 变成堆block ,我们在Build Settings中把ARC设置成MRC,
2022-02-21 15:00:21.668419+0800 Test[36114:1601315] ---block class:__NSStackBlock__
使用__NSStackBlock在访问外部变量时,会有什么问题?
会出现野指针crash 所以在ARC坏境Xcode帮我们处理成了堆block(NSMallocBlock)防止出现释放了还去访问导致野指针crash
void(^testBlock)(void);
void test(){
int age = 10;
testBlock = ^{
printf("---block age:%d",age);
};
}
int main(int argc, char * argv[]) {
@autoreleasepool {
test();
testBlock();
}
return 0;
}
testBock 访问了auto变量,所以是staticBlock,是存在栈内存上的,它捕获的变量age 也是保存在栈上的,出了其作用域就会自动销毁,所以我们再调用testBock()时,访问的是野指针,
如打印日志:
---block age:-303768616 (野指针)
当一个__NSStackBlock调用了copy操作,返回的就是一个__NSMallocBlock
int main(int argc, char * argv[]) {
@autoreleasepool {
int age = 10;
void(^block)(void) = [^{
NSLog(@"---block:Hello, World! %d",age);
} copy];
NSLog(@"---block class:%@",[block class]);
}
return 0;
}
日志:
2022-02-21 15:29:37.848290+0800 WidgetTest[37740:1628194] ---block class:__NSMallocBlock__
注意以上都是在MRC 下。
Block为什么使用copy修饰?
a、block在创建的时候默认分配的内存是在栈上,而不是在堆上。这样的话其本身的作用域是属于创建时候的作用域,一旦在创建的作用域之外调用就会导致程序的崩溃。所以使用了copy将其拷贝到堆内存上。
b、block创建在栈上,而block的代码中可能会用到本地的一些变量,只有将其拷贝到堆上,才能用这些变量
在ARC下 编译器会根据情况自动将栈上的block复制到堆上,
比如以下几种情况:
1.block作为函数返回值时
typedef void(^testBlock)(void);
testBlock test(){
int age = 10;
return ^{
NSLog(@"---block age:%d",age);
};
//MRC 正确的用法
/*
testBlock = [^{
printf("---block age:%d",age);
} copy];
*/
}
int main(int argc, char * argv[]) {
@autoreleasepool {
testBlock block = test();
block();
NSLog(@"---block class: %@",[block class]);
}
return 0;
}
testBock 访问了auto变量,所以是staticBlock,是存在栈内存上的,它捕获的变量age 也是保存在栈上的,
但是由于是在ARC 环境下,编译器自动将栈上的block复制到堆上
日志
2022-02-21 15:38:57.650081+0800 WidgetTest[38392:1637183] ---block age:10
2022-02-21 15:38:57.650534+0800 WidgetTest[38392:1637183] ---block class: __NSMallocBlock__
2. 将block赋值给__strong指针时
int main(int argc, char * argv[]) {
@autoreleasepool {
int age = 10;
testBlock block = ^{
NSLog(@"---block age:%d",age);
};
block();
NSLog(@"---block class: %@",[block class]);
}
return 0;
}
testBock 访问了auto变量,所以是staticBlock,是存在栈内存上的,它捕获的变量age 也是保存在栈上的,但是被强指针引用了, 在ARC 环境下,编译器自动将栈上的block复制到堆上
日志:
2022-02-21 15:46:59.251046+0800 WidgetTest[38850:1644202] ---block age:10
2022-02-21 15:46:59.251656+0800 WidgetTest[38850:1644202] ---block class: __NSMallocBlock__
3.block作为Cocoa API方法名含有UsingBlock的方法参数时
NSArray *arr = @[];
[arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
}];
4.block作为GCD API的方法参数时
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
});
5.block调用copy方法
总结:
1:一共有三种类型的Block.分为__NSGlobalBlock,__NSStackBlock,__NSMallocBlock.
没有访问 auto变量 的block 就是 __NSGlobalBlock
访问了auto变量 的block 就是 __NSStackBlock
当一个__NSStackBlock调用了copy操作,返回的就是一个__NSMallocBlock
2:在ARC环境下,编译器会自动把栈上的block copy到堆上
关于强弱引用
只要Block在栈上 不管是ARC 还是 MRC 都不会对捕获的auto变量进行强引用或者retain操作
当block被拷贝到堆上时:
1.会调用Block内部copy函数 这个函数内部会调用_Block_object_assign函数
2._Block_object_assign函数会对__block变量形成强引用(retain) (__block修饰的变量也会被拷贝到堆上)
3.只要是被__block修饰的变量 copy后都是强引用 如果没被__block修饰的对象 会根据对象的修饰符来确定是强引用还是弱引用
当Block从堆中移除的时候:
如果Block从堆上移除 会调用block内部的dispose函数 dispose函数内部会调用_Block_object_dispose函数 这个函数会自动断开引用的auto变量(断开这个引用) 相当于release
block内部的copy函数和dispose函数 只会在捕获对象auto变量的时候才有(因为对象需要内存管理) 捕获简单的数据变量比如Int的时候 是没有的
Tip:修改为MRC,
整个项目修改:在Targets->Build Settings->Apple Clang-Language-Objective-C-> Objective-C Automatic Reference Counting 为YES,
也可以对单个文件设置,还可以在Targets->Build Phases->Compils Source中设置某个文件的Compilter Flags 为-fno-objc-arc
参考:
https://www.jianshu.com/p/31f10267d6c7
https://www.cnblogs.com/junhuawang/p/14951598.html
https://www.jianshu.com/p/0a555501ade3
https://www.cnblogs.com/huanying2000/p/13950221.html