iOS 面試全方位剖析 -- Block篇


  • block 介紹
  • 截獲變量
  • __block修飾符
  • Block的內(nèi)存管理
  • Block的循環(huán)引用
  • 為什么 weakSelf 需要配合 strong self 使用

截獲變量

先看一個問題

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

- (void)method
{
     int multiplier = 6;
    int(^Block)(int) = ^int(int num)
    {

        return num * multiplier;
    };
    multiplier = 4;
    NSLog(@"result is %d", Block(2));
}


輸出是什么涝涤? 
 如果 將 int multiplier 改為靜態(tài)變量 static  int multiplier = 6辞州, 結(jié)果又是什么?

帶著問題往下看,有這幾種類型的變量

  • 局部變量 -- 基本數(shù)據(jù)類型 , 對象類型 (對于基本數(shù)據(jù)類型的局部變量截獲其值,對于對象類型的局部變量連同 所有權(quán)修飾符 一起截獲)
  • 靜態(tài)局部變量 ( 以指針形式截獲局部靜態(tài)變量 )
  • 全局變量 (不截獲全局變量)
  • 靜態(tài)全局變量 (不截獲靜態(tài)全局變量)

從這段代碼來深入了解一下

int global_var = 4;
static int static_global_var = 5;

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

使用 clang命令看一下編譯后的源碼 MCBlock.cpp
看這一段

  • 可以看到,局部變量截獲的就是它的值
  • 靜態(tài)局部變量以指針形式截取的
  • 對象類型的類型連同其修飾符一起截獲,理解這個就能更好的理解 Block 循環(huán)引用的問題,后續(xù)會說
  • 全局和靜態(tài)全局變量不截獲

然后回到問題

int multiplier  = 6 ,block(2)輸出的是 12,因為block執(zhí)行的時候截獲的是 6

static int multiplier  = 6 须误,block(2) 輸出的是8,因為截獲以指針形式截獲搔预,所以獲取到的  multiplier 是最新的值 4

__block修飾符

什么情況下需要用到 __block修飾符 呢霹期?
對被截獲變量進行賦值操作的時候 (區(qū)分 賦值 和使用)

看一些筆試題

  NSMutableArray *array = [NSMutableArray array];
    void(^block)(void) = ^{
        [array addObject:@123];
    };
    Block();

這里  對 array 只是一個使用,而不是賦值拯田,所以不需要 _ _block 進行修飾

  NSMutableArray *array = nil;
    void(^block)(void) = ^{
            array = [NSMutableArray array];
    };
    Block();

這里就需要在array的聲明處添加__block修飾符,不然編譯器會報錯

總結(jié)下历造,對變量進行賦值的時候,下面這些不需要__block修飾符

  • 靜態(tài)局部變量
  • 全局變量
  • 靜態(tài)全局變量
{
    __Block int multiplier = 6;
    int(^Block)(int) = ^int(int num)
    {

        return num * multiplier;
    };
    multiplier = 4;
    NSLog(@"result is %d", Block(2));
}
//這里的結(jié)果就是 8 了

加了 __block 修飾之后船庇,這個變量就變成了一個對象

在 multiplier 進行賦值的時候

__forwarding指向原來的對象, 通過 __forwarding 指針進行賦值吭产,修改掉 multiplier 的值


Block的內(nèi)存管理

Block類型

  • _NSConcreteGlobalBlock 全局
  • _NSConcreteStackBlock 棧類型
  • _NSConcreteMallocBlock 堆類型

看一下各個類型的Block在內(nèi)存上面的分配


Block的copy操作


比如現(xiàn)在聲明一個成員變量Block,而在棧上創(chuàng)建這個Block去賦值鸭轮,如果沒有對Block進行Copy操作的話臣淤,當我們通過成員變量去訪問這個Block的時候,可能會因為棧對應(yīng)的函數(shù)退出之后在內(nèi)存當中就銷毀掉了窃爷,繼續(xù)訪問就會引起內(nèi)存崩潰


Block的循環(huán)引用

下面這段代碼就會造成循環(huán)引用

  _array = [NSMutableArray arrayWithObject:@"block"];
    _strBlk = ^NSString*(NSString*num){
        return [NSString stringWithFormat:@"hello_%@",_array[0]];
    };
    _strBlk(@"hello");

self 持有 Block邑蒋,而 Block 里有成員變量 array, 持有 self,所以就造成了循環(huán)引用按厘,怎么解決呢?

  _array = [NSMutableArray arrayWithObject:@"block"];
  __weak NSArray *weakArray = _array;
    _strBlk = ^NSString*(NSString*num){
        return [NSString stringWithFormat:@"hello_%@",_array[0]];
    };
    _strBlk(@"hello");

為什么用_ _weak 修飾符解決循環(huán)引用? 這個其實在截獲變量里有講過医吊,截獲對象的時候會連同修飾符一起截獲,在外部定義的如果是 _ _weak 修飾符逮京,在 Block 里所產(chǎn)生的結(jié)構(gòu)體里面所持有的成員變量也是 _ _weak 類型

再看一段代碼卿堂,這樣寫有什么問題?

__block MCBlock*blockSelf = self;

    _blk = ^int(int num){
          //var = 2
              return num * blockSelf.var ;
    };
        _blk(3);

這樣在 ARC 模式下是會產(chǎn)生循環(huán)引用,引起內(nèi)存泄漏的



__block修飾后的指向是原來的對象,會造成循環(huán)引用
怎么解決呢,首先想到的當然是斷開其中一個環(huán)

__block MCBlock*blockSelf = self;

    _blk = ^int(int num){
          //var = 2
      int result = num * blockSelf.var;
      blockSelf = nil;
       return result;
    };
        _blk(3);

在調(diào)用完 blockSelf 后將它置為nil,斷開其中的一個環(huán)草描,就可以讓內(nèi)存得到釋放和銷毀
但是這樣會有一個弊端览绿,如果長期不調(diào)用這個block,這個循環(huán)引用的環(huán)就會一直存在

為什么 weakSelf 需要配合 strong self 使用

一般解決循環(huán)引用問題會這么寫

__weak typeof(self) weakSelf = self;
[self doSomeBackgroundJob:^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
        ...
    }
}];

為什么 weakSelf 需要配合 strongSelf 使用呢?
在 block 中先寫一個 strongSelf穗慕,其實是為了避免在 block 的執(zhí)行過程中饿敲,突然出現(xiàn) self 被釋放的尷尬情況。通常情況下逛绵,如果不這么做的話诀蓉,還是很容易出現(xiàn)一些奇怪的邏輯,甚至閃退暑脆。

比如下面這樣

__weak __typeof__(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [weakSelf doSomething];
    [weakSelf doOtherThing];

});

doSomething 內(nèi),weakSelf 不會被釋放.可是在執(zhí)行完第一個方法后 狐肢,weakSelf可能就已經(jīng)釋放掉添吗,再去執(zhí)行 doOtherThing,會引起 一些奇怪的邏輯份名,甚至閃退碟联。
所以需要這么寫

__weak __typeof__(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    __strong __typeof(self) strongSelf = weakSelf;
    [strongSelf doSomething];
    [strongSelf doOtherThing];
});

Block 面試總結(jié)

  • 什么是 Block?
  • 為什么 Block會產(chǎn)生循環(huán)引用?
  • 如何理解 Block 截獲變量的特性?
  • 你都遇到過哪些循環(huán)引用?怎么解決的?
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市僵腺,隨后出現(xiàn)的幾起案子鲤孵,更是在濱河造成了極大的恐慌,老刑警劉巖辰如,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件普监,死亡現(xiàn)場離奇詭異,居然都是意外死亡琉兜,警方通過查閱死者的電腦和手機凯正,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來豌蟋,“玉大人廊散,你說我怎么就攤上這事∥嗥#” “怎么了允睹?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長幌氮。 經(jīng)常有香客問我缭受,道長,這世上最難降的妖魔是什么浩销? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任贯涎,我火速辦了婚禮,結(jié)果婚禮上慢洋,老公的妹妹穿的比我還像新娘塘雳。我一直安慰自己陆盘,他們只是感情好,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布败明。 她就那樣靜靜地躺著隘马,像睡著了一般。 火紅的嫁衣襯著肌膚如雪妻顶。 梳的紋絲不亂的頭發(fā)上酸员,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機與錄音讳嘱,去河邊找鬼幔嗦。 笑死,一個胖子當著我的面吹牛沥潭,可吹牛的內(nèi)容都是我干的邀泉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼钝鸽,長吁一口氣:“原來是場噩夢啊……” “哼汇恤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拔恰,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤因谎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后颜懊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體财岔,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年饭冬,在試婚紗的時候發(fā)現(xiàn)自己被綠了使鹅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡昌抠,死狀恐怖患朱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情炊苫,我是刑警寧澤裁厅,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站侨艾,受9級特大地震影響执虹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜唠梨,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一袋励、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦茬故、人聲如沸盖灸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赁炎。三九已至,卻和暖如春钾腺,著一層夾襖步出監(jiān)牢的瞬間徙垫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工放棒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留姻报,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓间螟,卻偏偏與公主長得像逗抑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子寒亥,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

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

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 29,383評論 8 265
  • 前言 Blocks是C語言的擴充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,768評論 0 23
  • 《Objective-C高級編程》這本書就講了三個東西:自動引用計數(shù)荧关、block溉奕、GCD,偏向于從原理上對這些內(nèi)容...
    WeiHing閱讀 9,810評論 10 69
  • 終于擠出時間來了忍啤,而且心情也不錯加勤。這個雙十一讓我終身難忘,女主人公主動上班36小時后同波,千里迢迢來看我鳄梅。情景很像一首...
    壹土拾具閱讀 183評論 0 1
  • 爸爸想親安安,被安安一臉嫌棄的推開未檩!笑死我了戴尸! 抱在我懷里的時候,還會用小手拉我的衣服找奶吃冤狡,一本正經(jīng)的樣子孙蒙,可愛...
    Hao思嘉閱讀 295評論 0 0