前言
在閱讀該篇文章前,推薦閱讀
ios - block原理解讀(一)
前情提要
上篇文章理清了block的實現(xiàn)的基本思路,
提到了自動變量中基礎(chǔ)類型不能在block內(nèi)部進行修改。
那么全局變量,全局靜態(tài)變量,局部靜態(tài)變量呢?
本文解決問題
- block中引用靜態(tài)變量/全局變量/全局靜態(tài)變量
- 被block引用的對象,引用計數(shù)為何+=2?
- 循環(huán)引用問題、閉環(huán)開環(huán)的原因
局部靜態(tài)變量,進行可以修改原值:
int main(int argc, char * argv[]) {
@autoreleasepool {
static int a = 10;
void (^block)(void) = ^{
a++;
NSLog(@"%d",a);
};
block();
return 0;
}
}
這又是為什么呢?
同樣,我們看看編譯后的C++代碼
這里直接放出和前文不一樣的片段。


如果你仔細(xì)閱讀過前文,其他的我就不啰嗦了,
就是從值傳遞變成了指針傳遞,
也就是說,block內(nèi)部將靜態(tài)變量的地址存儲起來,那么用到的時候直接訪問其地址就好了。
問:老師???♂????♂?,我有個問題,如果block的作用域 > 這個靜態(tài)變量會輸出什么?
答:靜態(tài)變量存儲在靜態(tài)區(qū),程序結(jié)束后由系統(tǒng)釋放,所以不存在block的作用域大于靜態(tài)變量。
針對全局變量,一張截圖你就明白了

全局變量和靜態(tài)變量小科普:存儲同樣存儲在靜態(tài)區(qū),由系統(tǒng)管理
所以簡單來講,就是系統(tǒng)針對不同類型的變量的作用域和生命周期,做出了相應(yīng)的處理。
對象變量
在ARC自動引用計數(shù)下,當(dāng)引用計數(shù)為0時,對象會被釋放。
當(dāng)block內(nèi)部訪問該對象時,block對其強引用,
首先,通過兩段代碼,來看看一個問題:
typedef void (^Block)(void);
Block block;
int main(int argc, char * argv[]) {
@autoreleasepool {
TestObject *object = [[TestObject alloc] init];
NSLog(@"引用數(shù) %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
block = ^{
NSLog(@"%@",object);
};
NSLog(@"引用數(shù) %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
return 0;
}
}
輸出結(jié)果:
2019-02-21 20:54:37.526394+0800 BlockTest[70590:3745629] 引用數(shù) 1
2019-02-21 20:54:37.527116+0800 BlockTest[70590:3745629] 引用數(shù) 3
typedef void (^Block)(void);
Block block;
int main(int argc, char * argv[]) {
@autoreleasepool {
TestObject *object = [[TestObject alloc] init];
NSLog(@"引用數(shù) %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
{
block = ^{
NSLog(@"%@",object);
};
}
NSLog(@"引用數(shù) %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
return 0;
}
}
輸出結(jié)果:
2019-02-21 20:55:50.156887+0800 BlockTest[70627:3749548] 引用數(shù) 1
2019-02-21 20:55:50.157928+0800 BlockTest[70627:3749548] 引用數(shù) 2
先列出MRC和ARC下block的一點區(qū)別
MRC時代的block:
只要block引用外部局部變量,block放在棧里面。
ARC時代的block:
只要block引用外部局部變量,block就放在堆里面。
然后,再看一下c++源碼:


可以看出:
- 對象類型,多出了copy和dispose函數(shù)
- 原有的棧上的結(jié)構(gòu)體指針被copy到了堆,
同時,copy函數(shù)內(nèi)部會將棧對象指向堆對象。
如果你對copy函數(shù)有疑問,請查看ios - block原理解讀(三)
所以,在block初始化作用域內(nèi)引用計數(shù)+2,
在作用域外??臻g的結(jié)構(gòu)體被回收,引用計數(shù)-1,
在block消亡后,引用計數(shù)-1。
如果你理解了,看一下代碼,并說出結(jié)果:
typedef void (^Block)(void);
Block block;
int main(int argc, char * argv[]) {
@autoreleasepool {
TestObject *object = [[TestObject alloc] init];
NSLog(@"引用數(shù) %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
block = ^{
NSLog(@"%@",object);
};
block = nil;
NSLog(@"引用數(shù) %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
return 0;
}
}
答案:1,2
循環(huán)引用問題
在ARC大前提下:
- block對對象變量強引用
- 對象引用計數(shù)不為0則不會釋放
而所謂循環(huán)引用是指,多個對象之間相互引用,產(chǎn)生了閉環(huán)。
先上代碼:
typedef void (^Block)(void);
@interface ViewController ()
@property (nonatomic,copy) Block block;
@property (nonatomic,copy) NSString *name;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.block = ^{
NSLog(@"%@",self.name);
};
}
說明:
viewController現(xiàn)在持有block
通過上文我們已經(jīng)知道,
block又強引用了當(dāng)前的的viewController,
那么在ARC環(huán)境下,這兩個是不會釋放的,造成內(nèi)存泄露。
解決方法
既然造成了閉環(huán),又在想在block中希望使用viewController,
只能將閉環(huán)進行斷開。
初步方案,看代碼:
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%@",weakSelf.name);
};
繼續(xù)根據(jù)c++源碼分析
首先,__weak的作用是弱引用,不會增加引用計數(shù),
這個具體原理和__strong,__block在后續(xù)繼續(xù)講解。

然后,可以看到結(jié)構(gòu)體內(nèi)的屬性變成同樣是__weak類型的,
不會增加引用計數(shù)。
所以,下面代碼輸出結(jié)果是:1,1
int main(int argc, char * argv[]) {
@autoreleasepool {
TestObject *object = [[TestObject alloc] init];
__unsafe_unretained typeof(object) weakObject = object;
NSLog(@"引用數(shù) %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
block = ^{
NSLog(@"%@",weakObject);
};
NSLog(@"引用數(shù) %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
return 0;
}
}
所以,上面的閉環(huán)狀態(tài)被我們破壞了,現(xiàn)在僅僅是viewController強引用著block。
安全性
上面的方案,如果block內(nèi)部執(zhí)行時間比較長,在執(zhí)行時,viewController突然被釋放了,而block是在堆空間上,并不會被釋放,當(dāng)block內(nèi)部繼續(xù)訪問viewController,這個時候會出現(xiàn)野指針。
經(jīng)典解決方案:
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%@",weakSelf.name);
};
大部分博客只講到這個解決方案和所謂的短暫的閉環(huán),沒有將道理講明白。
其實,通過上篇文章和上面的解釋,我們已經(jīng)得出了結(jié)論。
首先,block引用的外部變量的是__weak修飾的weakSelf對象,
所以block初始化并copy到堆上,不會強引用self。
但是執(zhí)行block的時候,其實是執(zhí)行一個靜態(tài)函數(shù),
在執(zhí)行的過程中,生成了strongSelf對象,這個時候,產(chǎn)生了閉環(huán)。
但是這個strongSelf在??臻g上,在函數(shù)執(zhí)行結(jié)束后,strongSelf會被系統(tǒng)回收,此時閉環(huán)被打破。
注意:閉環(huán)不一定只局限于兩個對象,也可能是多個。
最后
以上均為個人研究和理解,如有問題,歡迎評論~
下篇將繼續(xù)解讀,敬請期待!