一、block循環(huán)引用
場景:從viewController通過modal跳轉(zhuǎn)到ModelViewController瓶盛,然后點(diǎn)擊屏幕返回ViewController乖菱。
如上兩圖坡锡,當(dāng)從modelViewController點(diǎn)擊屏幕返回viewController后蓬网,會(huì)調(diào)用dealloc方法窒所,打印出銷毀。說明ModelViewController在方法dismissViewController后帆锋,就銷毀了吵取。原因是從ViewController跳轉(zhuǎn)到ModelViewController時(shí),前者的self.presentedViewController屬性指向了ModelViewController锯厢,強(qiáng)指針皮官,不會(huì)釋放。而當(dāng)dismiss后实辑,self.presentedViewController屬性消失捺氢,沒有強(qiáng)指針指向,所以ModelViewController銷毀了剪撬。
而如下圖摄乒,在block中打印一下self的值,dismiss后残黑,就不會(huì)打印銷毀了(不調(diào)用dealloc方法)馍佑,控制器沒有銷毀。問題就出在第25行代碼梨水,造成block循環(huán)利用:block會(huì)對代碼塊中的所有強(qiáng)指針變量都請引用一次拭荤。就是25行的self被_block屬性指向了,而self又是ModelViewController疫诽,self指向了ModelViewController舅世,控制器要銷毀就要self釋放,但是self被_block指向奇徒,無法釋放雏亚。控制器無法銷毀逼龟,也導(dǎo)致了_block屬性不能釋放评凝,就造成了循環(huán)引用,造成內(nèi)存泄漏腺律。
解決辦法如下奕短,將self包裝成一個(gè)弱指針,運(yùn)行后匀钧,ModelViewController成功銷毀翎碑。
但是當(dāng)我們對block塊中的代碼有要求的時(shí)候,讓block代碼延時(shí)執(zhí)行的話之斯,就會(huì)因?yàn)榭刂破飨蠕N毀日杈,拿不到相應(yīng)的數(shù)據(jù)。如圖
解決方法如圖,將weakSelf包裝成強(qiáng)指針莉擒,這樣一來strongSelf又指向了model控制器酿炸,控制器又不會(huì)釋放了,而strongSelf是_block屬性方法體內(nèi)的局部變量涨冀,只有延時(shí)執(zhí)行完了填硕,strongSelf才會(huì)釋放,model控制器才會(huì)銷毀釋放鹿鳖。所以這樣就會(huì)順利的將_block方法執(zhí)行完才會(huì)銷毀控制器扁眯。
二、三種block
GlobalBlock::沒有使用局部變量 or 使用靜態(tài)or全局變量
MallocBlock:使用了局部變量 or OC屬性翅帜,并且使用__strong修飾的(被強(qiáng)引用的)姻檀,在堆區(qū)
StackBlock:使用了局部變量,但是沒有被強(qiáng)引用的
三涝滴、block變量截獲
1.block的一個(gè)特性就是變量截獲绣版,注意是截獲,而不是獲取狭莱〗┩蓿看圖
我們在block1塊前創(chuàng)建int變量b,然后在調(diào)用block1();之前改變b的值腋妙,然而打印出來的b=0默怨。block會(huì)截獲block塊之前的變量,一旦截獲骤素,后續(xù)的外部更改是沒有用的匙睹。
2.經(jīng)過第一點(diǎn)的描述,我們知道了block截獲之后,外部的更改對截獲值沒有影響济竹。那么在block內(nèi)部進(jìn)行更改呢痕檬?會(huì)發(fā)生什么呢?
可以看到送浊,block中對變量b進(jìn)行更改梦谜,編譯直接報(bào)錯(cuò)。說明在block塊中是不能對外部的變量值進(jìn)行更改的袭景,注意是變量的值唁桩,如果是外部的引用(非值類型),會(huì)有一些不同耸棒,后面細(xì)說荒澡。
從代碼中看出,block內(nèi)部不能改變外部變量的值与殃,會(huì)報(bào)錯(cuò)单山。但是為什么不能修改呢碍现?上代碼分析
從代碼分析中我們可以發(fā)現(xiàn),block塊中的b的地址與block外部的b的地址打印是不一樣的米奸。其實(shí)block的變量截獲昼接,block進(jìn)行了一些操作,block在內(nèi)部創(chuàng)建了臨時(shí)的變量躏升,用于記錄外部的截獲到的變量值辩棒,本質(zhì)上block塊中的b和外部的b不是同一個(gè)變量,所以在block中無法改變外部的變量的值膨疏。
3.然后我們解決一下第2點(diǎn)中加黑的問題,前面舉的例子都是值類型的變量截獲以及操作钻弄,那么引用類型呢佃却?我們這里使用NSMutableArray進(jìn)行測試。上代碼
是不是發(fā)現(xiàn)了不同窘俺?引用類型的變量block內(nèi)外的地址是一致的饲帅。再測試一下能不能在block中進(jìn)行更改?下面兩個(gè)測試案例瘤泪,兩種更改方法灶泵,上代碼
可以看到第一個(gè)圖中,編譯報(bào)錯(cuò)对途,第二個(gè)圖中正常運(yùn)行赦邻。我們首先要區(qū)分開值類型變量和引用類型變量的區(qū)別。值類型僅僅是棧中的一個(gè)數(shù)值实檀,而引用變量的實(shí)質(zhì)是一個(gè)指針惶洲,指針指向的(指針的值)是我們在堆中創(chuàng)建的對象的地址,而這個(gè)指針本身的地址是在棧中的膳犹。 是不是發(fā)現(xiàn)了點(diǎn)什么恬吕?block中不能改變外部變量的值其實(shí)指的是棧上變量的值?
圖一中我們的做法须床,是重新創(chuàng)建了一個(gè)可變數(shù)組對象铐料,然后讓array更新指向,指向我們新創(chuàng)建的對象的地址豺旬。在這種做法中钠惩,我們實(shí)質(zhì)上是試圖更改array指針的值(因?yàn)槲覀円屗赶蚱渌麑ο螅羔樀闹稻褪撬赶驅(qū)ο蟮牡刂?哈垢,而array指針本身地址是在棧上的妻柒,我們對block外部的棧上的變量進(jìn)行更改,就會(huì)導(dǎo)致編譯錯(cuò)誤耘分,無法更改举塔。
而圖二中绑警,表面上是對array進(jìn)行操作,其實(shí)是我們對array指針?biāo)赶虻牡刂?也就是數(shù)組對象央渣,堆中)進(jìn)行操作计盒,并沒有更改array的指向(值),所以是可以正常運(yùn)行的芽丹。證實(shí)了我們前面的猜測北启。block中不能改變外部變量的值,實(shí)質(zhì)上是不能改變處在棧中的變量的值拔第。
通過上面的分析咕村,我們可以發(fā)現(xiàn)蚊俺,其實(shí)我們上面對array進(jìn)行%p的地址打印懈涛,其實(shí)打印出的是array所指向的堆中對象的地址的打印泳猬,并不是array變量本身的地址。所以我們對array指針本身地址進(jìn)行打印得封,上代碼埋心。
發(fā)現(xiàn)了什么,是不是和之前第2點(diǎn)中拷呆,打印 值類型變量b地址 的結(jié)果是一致的晨横?block仍然是在block內(nèi)部生成了一個(gè)臨時(shí)變量洋腮,保存了截獲的arrar指針的值(讓我們可以通過保存的值正常訪問堆中的地址),所以block里面和外面的&array的打印不一致手形。
四啥供、__block底層原理
1.那么在上面的分析中,我們知道了伙狐,block中不能更改棧上變量的值瞬欧。那么有沒有辦法進(jìn)行更改呢?當(dāng)然是有的艘虎,我們可以使用__block關(guān)鍵字修飾我們想更改的變量,就可以實(shí)現(xiàn)更改属划。
2.依舊是為什么恬叹?為什么使用__block修飾之后就可以更改了呢同眯??
簡要概括一下硅确,之前的分析明肮,可以知道block中不能對棧上的變量進(jìn)行更改。__block關(guān)鍵字的原理其實(shí)就是:進(jìn)行了一個(gè)將棧上變量晤愧,拷貝到堆中的一個(gè)操作。即將棧上的基本數(shù)據(jù)類型,轉(zhuǎn)變封裝為一個(gè)對象類型的操作烙丛。以此來實(shí)現(xiàn)可以對變量進(jìn)行更改。
總結(jié):__block關(guān)鍵字其實(shí)就是進(jìn)行了一個(gè)堆棧的轉(zhuǎn)換操作钠右,將棧中的數(shù)據(jù)拷貝到堆上忘蟹,然后便可以進(jìn)行更改飒房。(就類似于函數(shù)傳參,我們只有傳入引用類型媚值,在函數(shù)中才能對其進(jìn)行真正的操作更改,否則是無法更改的)