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ì)在文件中找到以下代碼:

__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)代碼:

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é)果:

NSNumber *a;所以我們接著往下看
__attribute__((objc_ownership(strong))) NSNumber *a = ...這段代碼可以看到其修飾符為strong。我們修改strong為__unsafe_unretained來看下結(jié)果:
連同其修飾符一起截獲的。因此,在這里截獲了對(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é)果如下圖:

以指針的形式截取的。
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é)果如下圖:

不截獲。
3、總結(jié)

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