iOS - block原理解讀(二)

前言

在閱讀該篇文章前病涨,推薦閱讀
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++代碼

這里直接放出和前文不一樣的片段喷斋。

1.png
2.png

如果你仔細(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ì)全局變量,一張截圖你就明白了

全局變量圖示.png

全局變量和靜態(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++源碼:

image.png

image.png

可以看出:

  1. 對(duì)象類(lèi)型,多出了copy和dispose函數(shù)
  2. 原有的棧上的結(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大前提下:

  1. block對(duì)對(duì)象變量強(qiáng)引用
  2. 對(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ù)講解是己。

image.png

然后,可以看到結(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)期待!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蹲坷,一起剝皮案震驚了整個(gè)濱河市驶乾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌循签,老刑警劉巖级乐,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異县匠,居然都是意外死亡风科,警方通過(guò)查閱死者的電腦和手機(jī)罕扎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)丐重,“玉大人,你說(shuō)我怎么就攤上這事杆查“绲耄” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵亲桦,是天一觀的道長(zhǎng)崖蜜。 經(jīng)常有香客問(wèn)我,道長(zhǎng)客峭,這世上最難降的妖魔是什么豫领? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮舔琅,結(jié)果婚禮上等恐,老公的妹妹穿的比我還像新娘。我一直安慰自己备蚓,他們只是感情好课蔬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著郊尝,像睡著了一般二跋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上流昏,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天扎即,我揣著相機(jī)與錄音,去河邊找鬼况凉。 笑死谚鄙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的刁绒。 我是一名探鬼主播襟锐,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼膛锭!你這毒婦竟也來(lái)了粮坞?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤初狰,失蹤者是張志新(化名)和其女友劉穎莫杈,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體奢入,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡筝闹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年媳叨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片关顷。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡糊秆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出议双,到底是詐尸還是另有隱情痘番,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布平痰,位于F島的核電站汞舱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏宗雇。R本人自食惡果不足惜昂芜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赔蒲。 院中可真熱鬧泌神,春花似錦、人聲如沸舞虱。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)砾嫉。三九已至幼苛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間焕刮,已是汗流浹背舶沿。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留配并,地道東北人括荡。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像溉旋,于是被迫代替她去往敵國(guó)和親畸冲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容