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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容