- 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 修飾之后船庇,這個變量就變成了一個對象__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)引用?怎么解決的?