前言
在閱讀該篇文章前病涨,推薦閱讀
ios - block原理解讀(一)
前情提要
上篇文章理清了block的實(shí)現(xiàn)的基本思路历涝,
提到了自動(dòng)變量中基礎(chǔ)類(lèi)型不能在block內(nèi)部進(jìn)行修改。
那么全局變量,全局靜態(tài)變量峦阁,局部靜態(tài)變量呢钝腺?
本文解決問(wèn)題
- block中引用靜態(tài)變量/全局變量/全局靜態(tài)變量
- 被block引用的對(duì)象抛姑,引用計(jì)數(shù)為何+=2?
- 循環(huán)引用問(wèn)題拍屑、閉環(huán)開(kāi)環(huán)的原因
局部靜態(tài)變量途戒,進(jìn)行可以修改原值:
int main(int argc, char * argv[]) {
@autoreleasepool {
static int a = 10;
void (^block)(void) = ^{
a++;
NSLog(@"%d",a);
};
block();
return 0;
}
}
這又是為什么呢?
同樣僵驰,我們看看編譯后的C++代碼
這里直接放出和前文不一樣的片段喷斋。
如果你仔細(xì)閱讀過(guò)前文,其他的我就不啰嗦了蒜茴,
就是從值傳遞變成了指針傳遞星爪,
也就是說(shuō),block內(nèi)部將靜態(tài)變量的地址存儲(chǔ)起來(lái)粉私,那么用到的時(shí)候直接訪問(wèn)其地址就好了顽腾。
問(wèn):老師???♂????♂?,我有個(gè)問(wèn)題诺核,如果block的作用域 > 這個(gè)靜態(tài)變量會(huì)輸出什么抄肖?
答:靜態(tài)變量存儲(chǔ)在靜態(tài)區(qū),程序結(jié)束后由系統(tǒng)釋放窖杀,所以不存在block的作用域大于靜態(tài)變量漓摩。
針對(duì)全局變量,一張截圖你就明白了
全局變量和靜態(tài)變量小科普:存儲(chǔ)同樣存儲(chǔ)在靜態(tài)區(qū)入客,由系統(tǒng)管理
所以簡(jiǎn)單來(lái)講管毙,就是系統(tǒng)針對(duì)不同類(lèi)型的變量的作用域和生命周期,做出了相應(yīng)的處理桌硫。
對(duì)象變量
在ARC自動(dòng)引用計(jì)數(shù)下夭咬,當(dāng)引用計(jì)數(shù)為0時(shí),對(duì)象會(huì)被釋放铆隘。
當(dāng)block內(nèi)部訪問(wèn)該對(duì)象時(shí)卓舵,block對(duì)其強(qiáng)引用,
首先膀钠,通過(guò)兩段代碼掏湾,來(lái)看看一個(gè)問(wèn)題:
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的一點(diǎn)區(qū)別
MRC時(shí)代的block:
只要block引用外部局部變量,block放在棧里面托修。
ARC時(shí)代的block:
只要block引用外部局部變量忘巧,block就放在堆里面恒界。
然后睦刃,再看一下c++源碼:
可以看出:
- 對(duì)象類(lèi)型,多出了copy和dispose函數(shù)
- 原有的棧上的結(jié)構(gòu)體指針被copy到了堆十酣,
同時(shí)涩拙,copy函數(shù)內(nèi)部會(huì)將棧對(duì)象指向堆對(duì)象际长。
如果你對(duì)copy函數(shù)有疑問(wèn),請(qǐng)查看ios - block原理解讀(三)
所以兴泥,在block初始化作用域內(nèi)引用計(jì)數(shù)+2工育,
在作用域外棧空間的結(jié)構(gòu)體被回收搓彻,引用計(jì)數(shù)-1如绸,
在block消亡后,引用計(jì)數(shù)-1旭贬。
如果你理解了怔接,看一下代碼,并說(shuō)出結(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)引用問(wèn)題
在ARC大前提下:
- block對(duì)對(duì)象變量強(qiáng)引用
- 對(duì)象引用計(jì)數(shù)不為0則不會(huì)釋放
而所謂循環(huán)引用是指扼脐,多個(gè)對(duì)象之間相互引用,產(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);
};
}
說(shuō)明:
viewController現(xiàn)在持有block
通過(guò)上文我們已經(jīng)知道瓦侮,
block又強(qiáng)引用了當(dāng)前的的viewController,
那么在ARC環(huán)境下佣谐,這兩個(gè)是不會(huì)釋放的肚吏,造成內(nèi)存泄露。
解決方法
既然造成了閉環(huán)台谍,又在想在block中希望使用viewController须喂,
只能將閉環(huán)進(jìn)行斷開(kāi)。
初步方案趁蕊,看代碼:
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%@",weakSelf.name);
};
繼續(xù)根據(jù)c++源碼分析
首先坞生,__weak的作用是弱引用,不會(huì)增加引用計(jì)數(shù)掷伙,
這個(gè)具體原理和__strong,__block在后續(xù)繼續(xù)講解是己。
然后,可以看到結(jié)構(gòu)體內(nèi)的屬性變成同樣是__weak類(lèi)型的任柜,
不會(huì)增加引用計(jì)數(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強(qiáng)引用著block宅粥。
安全性
上面的方案参袱,如果block內(nèi)部執(zhí)行時(shí)間比較長(zhǎng),在執(zhí)行時(shí),viewController突然被釋放了抹蚀,而block是在堆空間上剿牺,并不會(huì)被釋放,當(dāng)block內(nèi)部繼續(xù)訪問(wèn)viewController环壤,這個(gè)時(shí)候會(huì)出現(xiàn)野指針晒来。
經(jīng)典解決方案:
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%@",weakSelf.name);
};
大部分博客只講到這個(gè)解決方案和所謂的短暫的閉環(huán),沒(méi)有將道理講明白郑现。
其實(shí)湃崩,通過(guò)上篇文章和上面的解釋?zhuān)覀円呀?jīng)得出了結(jié)論。
首先接箫,block引用的外部變量的是__weak修飾的weakSelf對(duì)象竹习,
所以block初始化并copy到堆上,不會(huì)強(qiáng)引用self列牺。
但是執(zhí)行block的時(shí)候整陌,其實(shí)是執(zhí)行一個(gè)靜態(tài)函數(shù),
在執(zhí)行的過(guò)程中瞎领,生成了strongSelf對(duì)象泌辫,這個(gè)時(shí)候,產(chǎn)生了閉環(huán)九默。
但是這個(gè)strongSelf在椪鸱牛空間上,在函數(shù)執(zhí)行結(jié)束后驼修,strongSelf會(huì)被系統(tǒng)回收殿遂,此時(shí)閉環(huán)被打破。
注意:閉環(huán)不一定只局限于兩個(gè)對(duì)象乙各,也可能是多個(gè)墨礁。
最后
以上均為個(gè)人研究和理解,如有問(wèn)題耳峦,歡迎評(píng)論~
下篇將繼續(xù)解讀恩静,敬請(qǐng)期待!