__block發(fā)揮作用的原理:
將棧上用__block修飾的自動(dòng)變量封裝成一個(gè)結(jié)構(gòu)體,讓其在堆上創(chuàng)建,以方便從棧上或堆上訪問和修改同一份數(shù)據(jù)。
Block 幾種類型通過代碼驗(yàn)證:
沒有訪問 auto變量 的block 就是 __NSGlobalBlock
auto變量:自動(dòng)變量,離開作用域就會(huì)銷毀,一般我們創(chuàng)建的局部變量都是auto變量 ,比如 int age = 10,系統(tǒng)會(huì)在默認(rèn)在前面加上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;
}
控制臺(tái)輸出: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;
}
打?。?/p>
2022-02-21 14:59:11.430473+0800 Test[35944:1599369] ---block class:__NSMallocBlock__
為什么打印NSMallocBlock,剛才不是說訪問了auto變量就是__NSStackBlock嗎?
因?yàn)檫@里我們使用的是ARC,在ARC環(huán)境下,Xcode編譯器再某些情況會(huì)默認(rèn)幫我們做調(diào)用copy 變成堆block ,我們?cè)贐uild Settings中把ARC設(shè)置成MRC,
2022-02-21 15:00:21.668419+0800 Test[36114:1601315] ---block class:__NSStackBlock__
使用__NSStackBlock在訪問外部變量時(shí),會(huì)有什么問題?
會(huì)出現(xiàn)野指針crash 所以在ARC壞境Xcode幫我們處理成了堆block(NSMallocBlock)防止出現(xiàn)釋放了還去訪問導(dǎo)致野指針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,是存在棧內(nèi)存上的,它捕獲的變量age 也是保存在棧上的,出了其作用域就會(huì)自動(dòng)銷毀,所以我們?cè)僬{(diào)用testBock()時(shí),訪問的是野指針,
如打印日志:
---block age:-303768616 (野指針)
當(dāng)一個(gè)__NSStackBlock調(diào)用了copy操作,返回的就是一個(gè)__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在創(chuàng)建的時(shí)候默認(rèn)分配的內(nèi)存是在棧上,而不是在堆上。這樣的話其本身的作用域是屬于創(chuàng)建時(shí)候的作用域,一旦在創(chuàng)建的作用域之外調(diào)用就會(huì)導(dǎo)致程序的崩潰。所以使用了copy將其拷貝到堆內(nèi)存上。
b、block創(chuàng)建在棧上,而block的代碼中可能會(huì)用到本地的一些變量,只有將其拷貝到堆上,才能用這些變量
在ARC下 編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上,
比如以下幾種情況:
1.block作為函數(shù)返回值時(shí)
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,是存在棧內(nèi)存上的,它捕獲的變量age 也是保存在棧上的,
但是由于是在ARC 環(huán)境下,編譯器自動(dòng)將棧上的block復(fù)制到堆上
日志
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指針時(shí)
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,是存在棧內(nèi)存上的,它捕獲的變量age 也是保存在棧上的,但是被強(qiáng)指針引用了, 在ARC 環(huán)境下,編譯器自動(dòng)將棧上的block復(fù)制到堆上
日志:
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的方法參數(shù)時(shí)
NSArray *arr = @[];
[arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
}];
4.block作為GCD API的方法參數(shù)時(shí)
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調(diào)用copy方法
總結(jié):
1:一共有三種類型的Block.分為__NSGlobalBlock,__NSStackBlock,__NSMallocBlock.
沒有訪問 auto變量 的block 就是 __NSGlobalBlock
訪問了auto變量 的block 就是 __NSStackBlock
當(dāng)一個(gè)__NSStackBlock調(diào)用了copy操作,返回的就是一個(gè)__NSMallocBlock
2:在ARC環(huán)境下,編譯器會(huì)自動(dòng)把棧上的block copy到堆上
關(guān)于強(qiáng)弱引用
只要Block在棧上 不管是ARC 還是 MRC 都不會(huì)對(duì)捕獲的auto變量進(jìn)行強(qiáng)引用或者retain操作
當(dāng)block被拷貝到堆上時(shí):
1.會(huì)調(diào)用Block內(nèi)部copy函數(shù) 這個(gè)函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)
2._Block_object_assign函數(shù)會(huì)對(duì)__block變量形成強(qiáng)引用(retain) (__block修飾的變量也會(huì)被拷貝到堆上)
3.只要是被__block修飾的變量 copy后都是強(qiáng)引用 如果沒被__block修飾的對(duì)象 會(huì)根據(jù)對(duì)象的修飾符來確定是強(qiáng)引用還是弱引用
當(dāng)Block從堆中移除的時(shí)候:
如果Block從堆上移除 會(huì)調(diào)用block內(nèi)部的dispose函數(shù) dispose函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù) 這個(gè)函數(shù)會(huì)自動(dòng)斷開引用的auto變量(斷開這個(gè)引用) 相當(dāng)于release
block內(nèi)部的copy函數(shù)和dispose函數(shù) 只會(huì)在捕獲對(duì)象auto變量的時(shí)候才有(因?yàn)閷?duì)象需要內(nèi)存管理) 捕獲簡(jiǎn)單的數(shù)據(jù)變量比如Int的時(shí)候 是沒有的
Tip:修改為MRC,
整個(gè)項(xiàng)目修改:在Targets->Build Settings->Apple Clang-Language-Objective-C-> Objective-C Automatic Reference Counting 為YES,
也可以對(duì)單個(gè)文件設(shè)置,還可以在Targets->Build Phases->Compils Source中設(shè)置某個(gè)文件的Compilter Flags 為-fno-objc-arc
參考:
http://www.itdecent.cn/p/31f10267d6c7
https://www.cnblogs.com/junhuawang/p/14951598.html
http://www.itdecent.cn/p/0a555501ade3
https://www.cnblogs.com/huanying2000/p/13950221.html