iOS探索:Block解析淺談

什么是Block

  • Block是將函數(shù)及其執(zhí)行上下文封裝起來的對象

接下來讓我們通過源碼來看一看Block的本質(zhì)

WX20181219-105950@2x.png
  • 我們在一個方法中寫了三行代碼辙浑,第一行是定義了一個局部變量伴嗡,第二行是一個Block排霉,第三行是這個Block的調(diào)用

這里我們通過一個clang的編譯命令clang -rewrite-objc xxx.m來看一下源碼的實現(xiàn)

WX20181219-111003@2x.png
  • 我們的那段代碼通過編譯器編寫后,首先第一行I代表的是一個實例方法后面的是對象和方法名,傳了兩個參數(shù)一個是self管毙,一個是選擇器因子

  • 然后我們方法中的第一行代碼在編譯后沒有發(fā)生改變嚎莉,我們著重看一下Block方法編譯后的改變

  • 首先我們可以看到__BlockOneObj__testMethod_block_impl_0這樣一個結(jié)構(gòu)體米酬,在這個結(jié)構(gòu)體中傳遞了幾個參數(shù),第一個參數(shù)(void*)__BlockOneObj__testMethod_block_func_0我們通過名字可以知道這是一個無類型的函數(shù)指針趋箩,第二個參數(shù)&__BlockOneObj__testMethod_block_desc_0_DATA是一個Block相關(guān)描述的結(jié)構(gòu)體然后取地址符赃额,第三個參數(shù)muIntNum就是我們定義的局部變量琼懊。最后取這個結(jié)構(gòu)體地址強制轉(zhuǎn)換賦值給我們定義的這個Block

然后我們來看看__BlockOneObj__testMethod_block_impl_0這個結(jié)構(gòu)體中有什么具體操作,如下圖

WX20181219-140037@2x.png

其中第一個結(jié)構(gòu)體里面又是什么數(shù)據(jù)結(jié)構(gòu)呢爬早,請看下圖

WX20181219-140536@2x.png

在我們上面介紹的結(jié)構(gòu)體下面還有一個函數(shù)哼丈,具體解釋請看下圖

WX20181219-141327@2x.png

那么什么是Block的調(diào)用呢

WX20181219-143015@2x.png

Block的調(diào)用其實就是函數(shù)的調(diào)用,從源碼中我們可以看出來

  • 首先先對這個Block進行一個強制類型轉(zhuǎn)換(__block_impl *)Block

  • 之后又取出它之中的成員變量FuncPtr(函數(shù)指針)筛严,找到我們上面解析的結(jié)構(gòu)體和函數(shù)醉旦,在其中拿到對應的函數(shù)調(diào)用,然后把其中的參數(shù)傳遞進去桨啃,一個參數(shù)是我們這個Block本身车胡,一個是我們傳遞的2,然后就回去調(diào)用__BlockOneObj__testMethod_block_func_0函數(shù)照瘾,最終進行調(diào)用

Block截獲變量

首先我們先來看一段代碼

- (void)testMethod {
    
    int muIntNum = 6;
    int(^Block)(int) = ^int(int num){
        return num *muIntNum;
    };
    
    muIntNum = 4;
    Block(2);
}

這段代碼執(zhí)行完Block(2)返回的值是多少呢匈棘?-------答案是12
接下來我們看一下為什么是12以及Block截獲變量的本質(zhì)是什么

  • 對于基本數(shù)據(jù)類型的局部變量截獲其值

  • 對于對象類型的局部變量連同其所有權(quán)修飾符一起截獲

  • 對于局部靜態(tài)變量是以指針形式去截獲

  • 對于全局變量和靜態(tài)全局變量不截獲

下面直接上代碼

#import "BlockTwoObj.h"

//全局變量
int global_var = 4;
//全局靜態(tài)變量
static int static_global_var = 5;

@implementation BlockTwoObj

- (void)testMethodTwo {
    
    //基本數(shù)據(jù)類型的局部變量
    int var = 1;
    //對象類型的的局部變量
    __unsafe_unretained id unsafe_obj = nil;
    __strong id strong_obj = nil;
    
    //局部靜態(tài)變量
    static int static_var = 3;
    
    void(^Block)(void) = ^{
        
        NSLog(@"基本數(shù)據(jù)類型局部變量:%d", var);
        NSLog(@"對象類型局部變量(__unsafe_unretained修飾):%@", unsafe_obj);
        NSLog(@"對象類型局部變量(__strong修飾):%@", strong_obj);
        
        NSLog(@"局部靜態(tài)變量:%d", static_var);
        
        NSLog(@"全局變量:%d", global_var);
        NSLog(@"全局靜態(tài)變量:%d", static_global_var);
    };
    
    Block();
}

@end

接下來我們通過clang命令clang -rewrite-objc -fobjc-arc xxx.m來看一下源碼

WX20181219-152500@2x.png

  • 在這張圖中可以很清晰的看到Block中的變量截獲,其中需要注意的是對于局部的靜態(tài)變量截獲的是指針析命,也就是說如果后面這個局部靜態(tài)變量發(fā)生了修改主卫,那么Block中使用的是最新的值

__block修飾符

我們在什么情況下使用__block修飾符呢?
一般情況下鹃愤,對被截獲變量進行賦值操作需要添加__block修飾符簇搅,這里需要注意的是賦值不等于是使用,切記H硗隆4窠!

例如在下面的代碼中是否需要__block修飾符來修飾

NSMutableArray *muArr = [[NSMutableArray alloc] init];
    
    void(^Block)(void) = ^{
        //這里只是做了添加操作凹耙,并非賦值姿现,所以不需要用__block進行修飾
        [muArr addObject:@"111"];
    };
    
    Block();

那么在下面的代碼段當中呢?

__block NSMutableArray *muArrOther = nil;
     void(^BlockOther)(void) = ^{
        //這里做了賦值操作肖抱,所以需要用__block進行修飾备典,否則會出現(xiàn)編譯報錯
        muArrOther = [NSMutableArray array];
    };
    
    BlockOther();

對變量進行賦值時

  • 需要__block修飾符修飾的是局部變量(包括基本數(shù)據(jù)類型和對象類型)

  • 不需要__block修飾符修飾的是靜態(tài)局部變量、全局變量和靜態(tài)全局變量虐沥,因為對于全局變量和靜態(tài)全局變量不涉及到變量的截獲熊经,而對于靜態(tài)局部變量呢,是通過使用指針來操作對應的變量的欲险,所以也不需要修飾

下面請看一段代碼镐依,還是我們上面的那個例子

- (void)testMethod {
    
    __block int muIntNum = 6;
    int(^Block)(int) = ^int(int num){
        return num *muIntNum;
    };
    
    muIntNum = 4;
    Block(2);
}

此時Block返回的是8,這里是為什么呢天试,我們只是用了__block來修飾

  • 因為在這里會發(fā)生一個非常奇妙的變化槐壳,__block修飾的變量變成了對象

請看下面的流程圖


WX20181219-162224@2x.png
  • 首先__block int muIntNum會被轉(zhuǎn)化成第一個這樣一個結(jié)構(gòu)體,其中具有isa指針喜每,我們也可以理解成一個對象

  • 從這個角度來看muIntNum經(jīng)過編譯后就會變成一個對象务唐,通過__forwarding指針去找到對應的對象雳攘,然后進行賦值

  • 剛才我們看到的代碼段是在棧上,在__block變量中有一個__forwarding指針枫笛,而這個指針指向的是自己吨灭,這里要注意的是前提是在棧上,如果在堆上刑巧,這個__forwarding指針指向的就不是自己了喧兄,在下面會講到

  • 所以在棧上我們修改這個變量的值,就會通過__forwarding指針找到自己本省去修改這個變量的值

那么這里有一個問題就是我們在棧上這個__forwarding指向的是自己到底有什么用呢啊楚?我們完全可以通過訪問成員變量來修改吠冤,為什么還需要這個指針呢,請繼續(xù)往下看

Block的內(nèi)存管理

Block有三種類型

  • _NSConcreteGlobalBlock 全局Block

  • _NSConcreteStackBlock 棧Block

  • _NSConcreteMallocBlock 堆Block

Block的Copy操作

WX20181219-172635@2x.png

  • 當我們棧上的Block通過copy在堆上產(chǎn)生一個一樣的Block恭理,有相同的Block和__block變量拯辙,當變量作用于結(jié)束后,棧上的Block對象就會被銷毀颜价,而堆上的block依舊存在涯保,所有如果棧上Block不用copy拷貝到堆上,在作用于銷毀后會因為找不到Block對象而崩潰

  • 當然我們在這里有一個問題拍嵌,假如說在MRC環(huán)境下遭赂,如果在棧上進行了copy操作,會不會產(chǎn)生內(nèi)存泄漏横辆,答案是肯定的,相當于一個對象alloc出來茄猫,但是并沒有對應的relese操作一樣

WX20181219-173758@2x.png
  • 當我們棧上的Block經(jīng)過copy操作后狈蚤,在堆上會產(chǎn)生一個一樣的Block,在棧中的Block中的__forwarding指針指向的事堆上Block的__block變量划纽,并且在堆上Block的__forwarding指針也是指向的它自己的__block變量

參考書籍

Objective - C 高級編程:iOS與OS X多線程和內(nèi)存管理

Github

Demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末脆侮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子勇劣,更是在濱河造成了極大的恐慌靖避,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件比默,死亡現(xiàn)場離奇詭異幻捏,居然都是意外死亡,警方通過查閱死者的電腦和手機命咐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門篡九,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人醋奠,你說我怎么就攤上這事榛臼∫恋瑁” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵沛善,是天一觀的道長航揉。 經(jīng)常有香客問我,道長金刁,這世上最難降的妖魔是什么迷捧? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮胀葱,結(jié)果婚禮上漠秋,老公的妹妹穿的比我還像新娘。我一直安慰自己抵屿,他們只是感情好庆锦,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著轧葛,像睡著了一般搂抒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上尿扯,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天求晶,我揣著相機與錄音,去河邊找鬼衷笋。 笑死芳杏,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的辟宗。 我是一名探鬼主播爵赵,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼泊脐!你這毒婦竟也來了空幻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤容客,失蹤者是張志新(化名)和其女友劉穎秕铛,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缩挑,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡但两,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年评抚,在試婚紗的時候發(fā)現(xiàn)自己被綠了缸榄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡衫哥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出悲关,到底是詐尸還是另有隱情谎僻,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布寓辱,位于F島的核電站艘绍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏秫筏。R本人自食惡果不足惜诱鞠,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望这敬。 院中可真熱鬧航夺,春花似錦、人聲如沸崔涂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冷蚂。三九已至缭保,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蝙茶,已是汗流浹背艺骂。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留隆夯,地道東北人钳恕。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像吮廉,于是被迫代替她去往敵國和親苞尝。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

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