iOS block變量截獲的那些事

1、前言

對(duì)于初中級(jí)iOS開發(fā)工程師來說,面試的時(shí)候手寫block是比較常見的問題,那對(duì)于高級(jí)及以上在問到block的使用的時(shí)候,不得不提block的變量截獲本質(zhì)了。在此我對(duì)此問題做一些總結(jié),僅供各位大佬借鑒、斧正。

2、實(shí)例探究

在開始之前,我們先來總結(jié)一下變量的幾種類型:
a.局部變量(基本數(shù)據(jù)類型)
b.局部變量(對(duì)象類型)
c.靜態(tài)局部變量
d.全局變量和靜態(tài)全局變量

下面開始我們一一分析;

a.局部變量 -- 基本數(shù)據(jù)類型

來看下面一段代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    int a = 10;
    void (^varBlock)(void) = ^{
        
        NSLog(@"%d", a); // 只會(huì)打印10
    };
    a = 20;
    varBlock();
}

然后我們使用clang命令clang -rewrite-objc ViewController.m來觀察,編譯后的ViewController.cpp文件代碼。

注:
如果上述命令報(bào)錯(cuò)fatal error: 'UIKit/UIKit.h' file not found,可換成clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m

我們會(huì)在文件中找到以下代碼:

局部變量 -- 基本數(shù)據(jù)類型
其中__ViewController__viewDidLoad_block_impl_0方法,為viewDidLoad編譯后的完整方法,在此可以看到我們定義的varBlock編譯為(后續(xù)重點(diǎn)關(guān)注的結(jié)構(gòu)體):

struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  int a;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

我們可以看下__block_impl這個(gè)結(jié)構(gòu)體:

struct __block_impl {
  void *isa; // 指向class的指針
  int Flags; // 標(biāo)識(shí)
  int Reserved; // 保留的變量
  void *FuncPtr; // 函數(shù)指針
};

所以我們可以說Block是將 函數(shù) 及其 執(zhí)行上下文 封裝起來的對(duì)象。上述的字段就不一一介紹了,感興趣的可以留言討論。
還記得我剛剛提到的重點(diǎn)么,可以看到最后截獲的變量int a就是已經(jīng)作為參數(shù)傳遞進(jìn)去了,并且是截獲的是變量a的值,所以后續(xù)無論怎么修改,我們block中的變量a的值,都是我們?cè)诙x時(shí)已經(jīng)傳遞進(jìn)去的值。也就是10。

補(bǔ)充:

接下來在這個(gè)地方補(bǔ)充一個(gè)面試比較常問的問題,也就是修改變量a的值,我們需要在block的聲明之前把int a = 10;修改為__block int a = 10;,下面來看一下__block修飾后的,變量截獲的問題,同樣的使用clang編譯文件,看一下重點(diǎn)代碼:

__block修飾下的變量截獲.jpg
我們看到,之前傳遞的變量int a變成了__Block_byref_a_0 *a,我們?cè)诳纯?code>__Block_byref_a_0這個(gè)是什么東西:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

找到了,原來__Block_byref_a_0也是一個(gè)含有isa的結(jié)構(gòu)體,也就是說,用__block修飾后,變量a變成了一個(gè)對(duì)象,并且把對(duì)象的地址傳遞給了block,所以可以在block中修改變量的值。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __block int a = 10;
    void (^varBlock)(void) = ^{
        
        NSLog(@"%d", a); // 打印結(jié)果為20
    };
    a = 20;
    varBlock();
}
b.局部變量 -- 對(duì)象類型

測試代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __strong NSNumber *a = @10;
    void (^varBlock)(void) = ^{
        
        NSLog(@"%@", a); // a = 10
    };
    varBlock();
}

我們看下上述代碼的編譯結(jié)果:

局部變量 -- 對(duì)象類型1.jpg
截獲的就僅僅是NSNumber *a;所以我們接著往下看
局部變量 -- 對(duì)象類型2.jpg
在圖2中可以看到__attribute__((objc_ownership(strong))) NSNumber *a = ...這段代碼可以看到其修飾符為strong。我們修改strong為__unsafe_unretained來看下結(jié)果:
局部變量 -- 對(duì)象類型3.jpg
其中修飾符變?yōu)?code>__attribute__((objc_ownership(none))) NSNumber *a = ...,由此,我們可以得出結(jié)論:對(duì)于對(duì)象類型的局部變量是連同其修飾符一起截獲的。因此,在這里截獲了對(duì)象的修飾符,所以強(qiáng)引用對(duì)象,在使用的時(shí)候可能為造成循環(huán)引用,導(dǎo)致內(nèi)存泄漏。

c.靜態(tài)局部變量

測試代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    static NSNumber *a;
    static int b = 20;
    void (^varBlock)(void) = ^{
        
        NSLog(@"%@ -- %d", a, b); // 10 -- 30
    };
    a = @10;
    b = 30;
    varBlock();
}

編譯結(jié)果如下圖:

靜態(tài)局部變量.jpg
可以清晰的看出,截獲的是其指針。結(jié)論:對(duì)于靜態(tài)局部變量是以指針的形式截取的。

d.全局變量和靜態(tài)全局變量

測試代碼:

@implementation ViewController

// 全局變量
int global_a = 10;

// 靜態(tài)全局變量
static int static_global_a = 20;

- (void)viewDidLoad {
    [super viewDidLoad];
    
    void (^varBlock)(void) = ^{
        
        NSLog(@"%d -- %d", global_a, static_global_a);
    };
    varBlock();
}

@end

編譯結(jié)果如下圖:

1557831286486.jpg
可以看出,沒有截獲。結(jié)論:對(duì)于全局變量和靜態(tài)全局變量不截獲。

3、總結(jié)

總結(jié).jpg

最后,祝大家早日成為大牛。歡迎留言交流。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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