Block探究

block的原理是怎樣埃叭?本質(zhì)是什么?

block本質(zhì)上也是一個OC對象晒他,block是封裝了函數(shù)調(diào)用與及調(diào)用環(huán)境的OC對象,源碼實例圖:

源碼實例圖

另外block布局底層結(jié)構(gòu)圖如下


布局圖


block的變量捕捉(capture)

為了保證block能夠正常訪問外部的變量,block有個變量捕捉機制

捕捉機制圖

auto 變量捕捉

auto變量捕捉圖1

另外auto變量的捕捉是值傳遞笼平,這個和static和全局變量都是不一樣的园骆,可以通過clang的源碼查看auto和static的區(qū)別:

auto static 測試圖
源碼展現(xiàn)圖

可以看到static是指針傳遞,而auto是值傳遞寓调,有這樣的差異是因為在執(zhí)行函數(shù)之后age就變成了垃圾數(shù)據(jù)锌唾,所以執(zhí)行block的時候,不可能去訪問age的內(nèi)存夺英,但是static 的內(nèi)存是一直貫穿整個運行的生命周期的晌涕?

全局變量,static全局變量痛悯,staitc局部變量


static 全局 局部
源碼圖

可以看到全局變量并沒有捕獲到block里面余黎,但是局部的static會,因為局部變量只能在局部中訪問灸蟆,并且是跨域訪問驯耻,所以為了能訪問正確,一定需要捕捉進去炒考,因為全局變量直接在data區(qū)域可缚,是可以直接訪問的,不需要捕獲進去斋枢,循環(huán)引用也是因為基于這種捕捉情況下產(chǎn)生的帘靡,例如

- (void)test{

? ? void (^block)(void) = ^{

? ? ? ? NSLog(@"-----%p",self);

????}

}

因為OC的機制,test方法會自動攜帶(id self,SEL _cmd)兩個默認參數(shù)瓤帚,所以可以當做是局部變量捕捉進去描姚,當出現(xiàn)block引用self涩赢,self又引用block的時候就會導(dǎo)致循環(huán)引用了,上面的例子轩勘,目前這樣看來當然是不會的筒扒,這個只是說明是導(dǎo)致循環(huán)引用的一個原理而已

對象類型的auto變量捕捉

當block內(nèi)部訪問了對象類型的auto變量時,如果block是在棧上绊寻,將不會auto變量產(chǎn)生強引用花墩,如果block被拷貝到堆上,會調(diào)用block內(nèi)部的copy函數(shù)澄步,copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)冰蘑,_Block_object_assign函數(shù)會根據(jù)auto變量的修飾符(__strong,__weak,__unsafe_unretained)作出相應(yīng)的操作,形成強引用(retain)或者弱引用,如果block從堆上移除村缸,會調(diào)用block內(nèi)部的dispose函數(shù)祠肥,dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù),_Block_object_dispose函數(shù)會自動釋放引用的auto變量(release)

調(diào)用時機圖

_Block_object_assign((void *)&dst->a,(void *)src->a,3/*BLOCK_FIELD_IS_BYREF*)

_Block_object_dispose((void *))src->a ,3/*BLOCK_FIELD_IS_BYREF*/);

對象類型的__block變量

_Block_object_assign((void *)&dst->p,(void *)src->p,8/*BLOCK_FIELD_IS_BYREF*/);

_Block_object_dispose((void *)&src-p,8/*BLOCK_FIELD_IS_OBJECT*/);

對象的auto和__block

當__block被拷貝到堆上梯皿,會調(diào)用__block內(nèi)部的copy函數(shù)仇箱,copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù),_Block_object_assign函數(shù)會根據(jù)auto變量的修飾符(__strong,__weak,__unsafe_unretained)作出相應(yīng)的操作索烹,形成強引用(retain)或者弱引用(注意:這里僅僅限于ARC時會retain工碾,MRC時不會retain),如果block從堆上移除,會調(diào)用block內(nèi)部的dispose函數(shù)百姓,dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù),_Block_object_dispose函數(shù)會自動釋放引用的auto變量(release)

block的類型

block有3中類型况木,可以通過調(diào)用class方法或者isa指針查看具體類型垒拢,最終都是繼承NSBlock類型

__NSGlobalBlock__(_NSConcreteGlobalBlock)

__NSStackBlock__(_NSConcreteStackBlock)

__NSMallocBlock__(_NSConcreteMallocBlock)

block的類型圖1


block的類型圖2

證明:

block style類型證明圖

從圖中我們可以看到這和上面的圖片描述不一樣,原因是什么呢火惊?在解釋這個問題之前求类,我們在看看這個例子:

stackblock圖

從上面的圖是不是可以悟出什么東西來呀,上面arc的情況下能正常打印結(jié)果屹耐,說明了arc幫我們額外做了一些操作尸疆,例如copy block從棧到堆,打印[block copy] 的值和block的地址值是一樣就說明了這一點惶岭,另外從clang出來的全部都是satckblock類型的寿弱,說明llvm編譯器在運行時幫我們做了一些轉(zhuǎn)換類型的操作

mrc 模擬 arc

block中的copy

ARC環(huán)境下,編譯器會根據(jù)情況自動將棧上的block復(fù)制到堆上按灶,比如以下情況:

1.block作為函數(shù)返回值時

SLBlock myblock(){

? ? int age = 10;

? ? return ^{

? ? ? ? NSLog(@"------%d",age);

? ? }

}

終端打印結(jié)果圖:

block作為返回值

注意:如果沒有age1變量的話症革,因為沒有訪問auto變量,會當做全局變量處理鸯旁,打印出來是global類型

2.將block賦值給__strong指針時

int main(int argc,const char* argv[]) {

? ? @autoreleasepool {

? ? ? ? int age =10;

? ? ? ? SLBlock block = ^{//默認是strong指針引用

? ? ? ? ? ? NSLog(@"---------%d", age);

? ? ? ? };

? ? ? ? NSLog(@"%@", [block class]);

?}

? ? NSLog(@"%@", [block class]);

}

如果將上述的代碼更改成

int age = 10

NSLog(@"%@",[^{NSLog(@"----------%d",age);} class]);

這個時候這個打印應(yīng)該是stack噪矛,因為沒有strong指針引用


3.block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時

4.block作為GCD API的方法參數(shù)時

MRC下block屬性的建議寫法

@property (copy, nonatomic) void(^block)(void);

ARC下block屬性的建議寫法

@property (strong, nonatomic) void(^block)(void);

@property (copy, nonatomic) void(^block)(void);

__blcok 修飾符

__block 可以用于解決block內(nèi)部無法修改auto變量值的問題量蕊,__block不能修飾全局變量,靜態(tài)變量(static),編譯器會將__block變量包裝成一個對象

__block 聲明變量圖
auto 聲明變量圖

__block 的內(nèi)存管理

當block在棧上艇挨,并不會對__block變量產(chǎn)生強引用残炮,當block被copy到堆上時,會調(diào)用block內(nèi)部的copy函數(shù)缩滨,copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)势就,_Block_object_assign函數(shù)會對_block變量形成強引用(retain),如下圖所示:

block copy 到堆中強引用圖

當block從堆中移除時楷怒,會調(diào)用block內(nèi)部的dispose函數(shù)蛋勺,dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù),_Block_object_dispose 函數(shù)會自動釋放引用的_block變量(release),如下圖所示:


block dispose圖

從clang出來的源碼中,我們可以發(fā)現(xiàn)里面__block修飾的會有一個__forwarding指針鸠删,這個指針是干嘛用的呢抱完?然而這里__forwarding指針真的永遠指向自己么?我們來做一個實驗

MRC?

可以看到MRC環(huán)境下刃泡,var->forwarding->var(age的指針)是相同的巧娱,說明棧中的block并沒有拷貝到堆中,并且此時的forwarding指向的就是自己棧上block的內(nèi)存地址烘贴,再看一下這個情形:

MRC
ARC

可以看到MRC下block使用copy禁添,和ARC下直接定義是一樣的情況,說明ARC下桨踪,自動幫我們將strong 引用的block拷貝到了堆中老翘,這個時候,可以發(fā)現(xiàn)兩個age的地址值并不一樣锻离,由此可以說明_forwarding 指針指向的不是同一個并且由打印結(jié)果age的值可以看到铺峭,block里面age的值和block外age的值是一樣,都是30汽纠,所以在使用__block之后卫键,在棧中訪問age的時候,val->forwarding->val 訪問的也是堆中的age值虱朵,在堆中訪問age的時候莉炉,val->forwarding->val 訪問的是堆中的age值,那這個說明了forwarding是為了讓我們更好的管理內(nèi)存的碴犬,不論現(xiàn)在block是出于棧中還是堆中絮宁,都不會影響到尋找到的相關(guān)信息,當block是在棧中翅敌,__forwarding指向的就是棧本身的地址羞福,當block copy到堆中的時候,__forwarding指針指向的就是堆本身的地址蚯涮,如下圖所示:

__forwarding指針

上面中有沒有人存在一個這樣的問題治专,就是age的地址值卖陵,到底是__Block_byref_age_0 *age 的地址值,還是__Block_byref_age_0 *age 里面的變量age的地址值呢张峰?我們來證明一下:

實例圖
證明圖


__weak問題

在使用clang轉(zhuǎn)換OC為C++代碼時泪蔫,可能會遇到以下問題

cannot create __weak reference in file using manual reference

解決方案:支持ARC,指定運行時系統(tǒng)版本喘批,比如:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main.cpp

循環(huán)引用

例子1:

測試例子
打印結(jié)果

可以看到在打印之前撩荣,person因為出了作用域,應(yīng)該被銷毀才對的饶深,但是這里斌沒有執(zhí)行dealloc操作餐曹,說明person沒有被釋放,內(nèi)存泄漏了敌厘,我們來分析一下內(nèi)存分布圖:

例子1內(nèi)存分布圖

可以看到運行到大括號外面的時候台猴,箭頭1銷毀,但是這個時候2俱两,3還是相互引用饱狂,導(dǎo)致不能釋放,所以出現(xiàn)了循環(huán)引用宪彩,這個時候應(yīng)該怎么解決呢休讳?直接讓其中一根線是弱引用就可以了,3弱引用也可以尿孔,2弱引用也可以俊柔,因為這里block是person的一個屬性,所以想person銷毀的時候活合,block就銷毀婆咸,那么我這里就是讓block里面的引用弄成弱引用就可以了,例如下圖所示:

弱引用

相應(yīng)的代碼更改成為:

代碼改造圖
打印結(jié)果圖

大括號結(jié)束后芜辕,引用1銷毀,person沒有強引用指向块差,person銷毀侵续,person銷毀之后,因為3是若引用憨闰,所以block銷毀状蜗,內(nèi)存循環(huán)應(yīng)用解除,這里除了用__weak 之外鹉动,使用__unsafe__unretained也不會產(chǎn)生強引用轧坎,但是不安全,指向的對象銷毀之后泽示,指針存儲的地址不變缸血,再次訪問的時候會導(dǎo)致野指針蜜氨,__weak會賦值成nil,安全捎泻;當然通過__block 也可以飒炎,具體可以看下面的補充要點;

補充要點

1.block的屬性修飾詞為什么是copy笆豁?使用block有哪些使用注意郎汪?

block一旦沒有進行copy操作,就不會在堆上闯狱,需要注意循環(huán)引用問題,例如下圖展示:

循環(huán)引用圖

使用ARC解決循環(huán)引用

arc 解決循環(huán)引用

使用MRC解決循環(huán)引用

MRC解決循環(huán)引用

MRC情況下煞赢,如果不加__unsafe_unretained ,或者__block 修飾的話,如果block使用了copy拷貝到堆上的時候哄孤,因為聲明的變量是強引用照筑,在內(nèi)部會對變量使用retain一次,這樣最后就會導(dǎo)致強引用了录豺,也就是會循環(huán)引用

可以添加微信一起交流學習:fslskz

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末朦肘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子双饥,更是在濱河造成了極大的恐慌媒抠,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咏花,死亡現(xiàn)場離奇詭異趴生,居然都是意外死亡,警方通過查閱死者的電腦和手機昏翰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門苍匆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人棚菊,你說我怎么就攤上這事浸踩。” “怎么了统求?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵检碗,是天一觀的道長。 經(jīng)常有香客問我码邻,道長折剃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任像屋,我火速辦了婚禮怕犁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己奏甫,他們只是感情好戈轿,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著扶檐,像睡著了一般凶杖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上款筑,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天智蝠,我揣著相機與錄音,去河邊找鬼奈梳。 笑死杈湾,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的攘须。 我是一名探鬼主播漆撞,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼于宙!你這毒婦竟也來了浮驳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤捞魁,失蹤者是張志新(化名)和其女友劉穎至会,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谱俭,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡奉件,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了昆著。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片县貌。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖凑懂,靈堂內(nèi)的尸體忽然破棺而出煤痕,到底是詐尸還是另有隱情,我是刑警寧澤接谨,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布杭攻,位于F島的核電站,受9級特大地震影響疤坝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜馆铁,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一跑揉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦历谍、人聲如沸现拒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽印蔬。三九已至,卻和暖如春脱衙,著一層夾襖步出監(jiān)牢的瞬間侥猬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工捐韩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留退唠,地道東北人。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓荤胁,卻偏偏與公主長得像瞧预,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子仅政,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

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

  • 在我們實際的開發(fā)過程中垢油,block的使用可以說是經(jīng)常遇到到的了吧,GCD圆丹,網(wǎng)絡(luò)請求滩愁,動畫都隨處可見block的影子...
    飛奔的小鯊魚閱讀 284評論 0 0
  • 提示:下面會把OC相應(yīng)的類轉(zhuǎn)化為C++代碼,OC代碼轉(zhuǎn)C++代碼的生成 一、block 知識回顧block 是一個...
    IIronMan閱讀 676評論 0 2
  • 一运褪、Block的底層結(jié)構(gòu)及本質(zhì) (1)block本質(zhì): 從代碼可以看出惊楼,Block的本質(zhì)就是NSObject. 也...
    王的for閱讀 527評論 0 2
  • 參考篇:iOS-Block淺談 前言:本文簡述Block本質(zhì),如有錯誤請留言指正秸讹。 第一部分:Block本質(zhì) Q:...
    夢蕊dream閱讀 61,224評論 41 322