block
- Block介紹
- 截獲變量
- __block修飾符
- Block的內(nèi)存管理
- Block的循環(huán)引用
什么是Block?
Block是將函數(shù)及其執(zhí)行上下文封裝起來(lái)的對(duì)象。
//想要更徹底的理解block需要clang它的中間碼
clang -rewrite-objc file.m 查看編譯之后的文件內(nèi)容
- Block是一個(gè)對(duì)象
- 對(duì)象封裝了函數(shù)和執(zhí)行上下文
block的調(diào)用
Block調(diào)用就是函數(shù)的調(diào)用
截獲變量
下述代碼的結(jié)果是16,因?yàn)閎lock對(duì)局部變量multiplier進(jìn)行了截獲
int multiplier = 8;
int(^MuBlock)(int) = ^int(int num){
return num * multiplier;
};
multiplier = 6
MuBlock(2);
- 局部變量
- 基本數(shù)據(jù)類型
- 對(duì)象類型
- 靜態(tài)局部變量
- 全局變量
- 靜態(tài)全局變量
什么是block截獲變量?
- 基本數(shù)據(jù)類型的局部變量截獲其值
- 對(duì)于對(duì)象類型的局部變量聯(lián)通所有權(quán)修飾符一起截獲
- 以指針形式截獲局部靜態(tài)變量
- 不截獲全局變量、靜態(tài)全局變量
如何理解我們需要編譯中間碼來(lái)幫我理解
//因?yàn)樵贏RC中的block跟MRC不一樣
//因?yàn)镸RC中沒有block循環(huán)引用問題,下面會(huì)有解釋
clang -rewrite-objc -fobjc-arc file.m
編譯代碼,以及OC代碼如下:
struct __MyBlock__MyMethod_block_impl_0 {
struct __block_impl impl;
struct __MyBlock__MyMethod_block_desc_0* Desc;
//截獲局部變量的值(基本數(shù)據(jù)類型)
int var;
//連同所有權(quán)修飾符一起截獲
__unsafe_unretained id unsafe_obj;
__strong id strong_obj;
//以指針形式截獲靜態(tài)局部變量
int *static_var;
//對(duì)全局變量、靜態(tài)全局變量不截獲
__MyBlock__MyMethod_block_impl_0(void *fp, struct __MyBlock__MyMethod_block_desc_0 *desc, int _var, __unsafe_unretained id _unsafe_obj, __strong id _strong_obj, int *_static_var, int flags=0) : var(_var), unsafe_obj(_unsafe_obj), strong_obj(_strong_obj), static_var(_static_var) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//全局變量
int global_var = 6;
//靜態(tài)全局變量
static int static_global_var = 8;
- (void)MyMethod{
//基本數(shù)據(jù)類型的變量
int var = 1;
//對(duì)象類型的局部變量
__unsafe_unretained id unsafe_obj = nil;
__strong id strong_obj = nil;
//靜態(tài)局部變量
static int static_var = 3;
void(^Block)(void) = ^{
NSLog(@"局部變量<基本數(shù)據(jù)類型> var %d",var);
NSLog(@"局部變量<__unsafe_unretained 對(duì)象類型> unsafe_obj %@",unsafe_obj);
NSLog(@"局部變量<__strong 對(duì)象類型> strong_obj %@",strong_obj);
NSLog(@"靜態(tài)局部變量 static_var %d",static_var);
NSLog(@"全局變量 global_var %d",global_var);
NSLog(@"靜態(tài)全局變量 static_global_var %d",static_global_var);
};
Block();
}
截獲變量的總結(jié)
- 局部變量
- 基本數(shù)據(jù)類型
- 截獲其值
- 對(duì)象類型
- 指針連同所有權(quán)一起截獲
- 基本數(shù)據(jù)類型
- 靜態(tài)局部變量
- 截獲其指針
- 全局變量
- 不截獲
- 靜態(tài)全局變量
- 不截獲
__block修飾符
什么場(chǎng)景需要使用__block?
一般情況下,對(duì)截獲變量進(jìn)行賦值(跟使用操作不一樣)操作需添加__block修飾符。
例子:下面代碼有什么問題嗎?
- (void)test{
NSMutableArray *array = [NSMutableArray array];
void(^Block)(void) = ^{
[array addObject:@21];
};
Block();
}
答案:沒有問題,因?yàn)閍rray并沒有去賦值。不需要使用__block。
再來(lái)看一個(gè)例子:下面代碼有什么問題嗎?
- (void)test{
NSMutableArray *array = nil;
void(^Block)(void) = ^{
array = [NSMutableArray array];
};
Block();
}
答案: array需要添加__block修飾符。
對(duì)變量進(jìn)行賦值時(shí),合適需要添加__block?
- 需要添加__block修飾符的情況?
- 局部變量
- 基本數(shù)據(jù)類型
- 對(duì)象類型
- 局部變量
- 不需要添加__block修飾符
- 靜態(tài)局部變量
- 全局變量
- 靜態(tài)全局變量
__block 修飾的變量最終變成了什么?
還是通過clang 命令來(lái)編譯出中間碼
- (void)MyMethod{
__block int multiplier = 8;
int(^MuBlock)(int) = ^int(int num){
return num * multiplier;
};
multiplier = 6;
NSLog(@"%d",MuBlock(2));
}
// @implementation MyBlock
struct __Block_byref_multiplier_0 {
//因?yàn)橛衖sa指針
void *__isa;
//指向同類指針的__forwarding
__Block_byref_multiplier_0 *__forwarding;
int __flags;
int __size;
//我們截獲的變量
int multiplier;
};
綜合上述說明通過__block修飾的變量最終變成了對(duì)象,如果在OC代碼中直接修改這個(gè)變量的值,就會(huì)走下面的步驟
static void _I_MyBlock_MyMethod(MyBlock * self, SEL _cmd) {
__attribute__((__blocks__(byref))) __Block_byref_multiplier_0 multiplier = {(void*)0,(__Block_byref_multiplier_0 *)&multiplier, 0, sizeof(__Block_byref_multiplier_0), 8};
int(*MuBlock)(int) = ((int (*)(int))&__MyBlock__MyMethod_block_impl_0((void *)__MyBlock__MyMethod_block_func_0, &__MyBlock__MyMethod_block_desc_0_DATA, (__Block_byref_multiplier_0 *)&multiplier, 570425344));
//打印代碼之前,我們給這個(gè)__block修飾的變量對(duì)象的值賦值到了6
(multiplier.__forwarding->multiplier) = 6;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8z_j2rlh3t16m9cdrljdz_r67380000gn_T_Myblock_5bd0fd_mi_0,((int (*)(__block_impl *, int))((__block_impl *)MuBlock)->FuncPtr)((__block_impl *)MuBlock, 2));
}
//block方法實(shí)現(xiàn)
static int __MyBlock__MyMethod_block_func_0(struct __MyBlock__MyMethod_block_impl_0 *__cself, int num) {
__Block_byref_multiplier_0 *multiplier = __cself->multiplier; // bound by ref
return num * (multiplier->__forwarding->multiplier);
}
打印代碼之前,給這個(gè)__block修飾的變量對(duì)象的值賦值到了6,并且在block中的實(shí)現(xiàn)是直接通過(multiplier->__forwarding->multiplier)來(lái)進(jìn)行操作,所以打印結(jié)果應(yīng)該會(huì)是修改過后的值。也就是12.

__forwarding存在的意義?
- 不論在任何內(nèi)存位置,都可以訪問同一個(gè)__block變量。
__block相關(guān)的筆試題
- (void)MyMethod{
__block int multiplier = 8;
int(^MuBlock)(int) = ^int(int num){
return num * multiplier;
};
multiplier = 6;
NSLog(@"%d",MuBlock(2));
}
上述調(diào)用結(jié)果是12.
Block的內(nèi)存管理
- _NSConcreteGlobalBlock 全局block
- _NSConcreteStackBlock 棧block
- _NSConcreteMallocBlock 堆block
Block的內(nèi)粗分布:

Block的copy操作

聲明一個(gè)成員變量是block,如果這個(gè)block使用的是assign,通過成員變量去訪問block的話,可能棧所對(duì)應(yīng)的函數(shù)退出之后,在內(nèi)存中就銷毀掉了,所以繼續(xù)訪問可能崩潰
棧上Block的copy
當(dāng)我們?cè)跅I厦娴腷lock進(jìn)行copy以后,那么在MRC環(huán)境下是否會(huì)引起內(nèi)存泄漏?
產(chǎn)生內(nèi)存泄漏。

棧上的Block的銷毀
棧上,等作用域

棧上__block變量的copy
__block修飾的變量都會(huì)轉(zhuǎn)換成一個(gè)對(duì)象,而這個(gè)對(duì)象被copy的時(shí)候會(huì)在堆上面開辟一塊空間生成一個(gè)全新的__block修飾的變量對(duì)象,而這兩個(gè)對(duì)象的__forwarding指針都指向了堆上面的對(duì)象。如果沒有做copy操作那么就使用的是棧上面的__block對(duì)象。

Block代碼的解讀
@property(nonatomic,copy)MuBlock blk;
- (void)MyMethod{
//用__block修飾的變量會(huì)在棧上生成一個(gè)block對(duì)象,里面有__forwarding指針和成員變量multiplier。__forwarding指針指向這個(gè)block對(duì)象自己
__block int multiplier = 8;
//當(dāng)我向_blk屬性賦值的時(shí)候?qū)嶋H上是對(duì)這個(gè)block進(jìn)行copy,所以這個(gè)block會(huì)在堆上開辟一個(gè)空間。在堆上有另外一個(gè)副本
_blk = ^int(int num){
return num * multiplier;
};
/這個(gè)時(shí)候修改的值是__forwarding指向的對(duì)象的成員變量multiplier,因?yàn)開blk實(shí)際上是堆上面的block,下面的代碼實(shí)際上是修改堆上面的__block對(duì)象
multiplier = 6;
[self excMuBlock];
}
- (void)excMuBlock{
int result = _blk(4);
NSLog(@"%d",result);
結(jié)果應(yīng)該是24.
}
代碼分析題
block循環(huán)引用的問題
下面代碼有何問題?
//在ARC環(huán)境下
- (void)MyMethod{
//默認(rèn)是strong屬性
_array = [NSMutableArray arrayWithObjects:@"111", nil];
//默認(rèn)是copy屬性
//_array、 _strblk都是當(dāng)前屬性的_array是strong,_strblk是copy
_strblk = ^NSString *(NSString * str){
return [NSString stringWithFormat:@"%@",_array[0]];
};
_strblk(@"hello");
}
下面繼續(xù)分析clang代碼
static void _I_MyBlock_MyMethod(MyBlock * self, SEL _cmd) {
(*(NSMutableArray *__strong *)((char *)self + OBJC_IVAR_$_MyBlock$_array)) = ((NSMutableArray * _Nonnull (*)(id, SEL, ObjectType _Nonnull __strong, ...))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("arrayWithObjects:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_8z_j2rlh3t16m9cdrljdz_r67380000gn_T_Myblock_e9f218_mi_0, __null);
(*(__strong xhBlock *)((char *)self + OBJC_IVAR_$_MyBlock$_strblk)) = ((NSString *(*)(NSString *__strong))&__MyBlock__MyMethod_block_impl_0((void *)__MyBlock__MyMethod_block_func_0, &__MyBlock__MyMethod_block_desc_0_DATA, self, 570425344));
((NSString *(*)(__block_impl *, NSString *__strong))((__block_impl *)(*(__strong xhBlock *)((char *)self + OBJC_IVAR_$_MyBlock$_strblk)))->FuncPtr)((__block_impl *)(*(__strong xhBlock *)((char *)self + OBJC_IVAR_$_MyBlock$_strblk)), (NSString *)&__NSConstantStringImpl__var_folders_8z_j2rlh3t16m9cdrljdz_r67380000gn_T_Myblock_e9f218_mi_2);
}
由中間碼我們可以分析出來(lái),當(dāng)在block中使用_array的時(shí)候?qū)嶋H上是通過(*(NSMutableArray *__strong *)((char *)self + OBJC_IVAR_$_MyBlock$_array))這樣的形式獲取到_array,這樣的話,相當(dāng)于,在self中的block中又強(qiáng)引用了self這樣就是循環(huán)引用。自循環(huán)引用
解決方案:
//第一種
- (void)MyMethod{
_array = [NSMutableArray arrayWithObjects:@"111", nil];
__weak NSArray *weakarray = _array;
_strblk = ^NSString *(NSString * str){
return [NSString stringWithFormat:@"%@",weakarray[0]];
};
_strblk(@"hello");
}
//第二種
- (void)MyMethod{
_array = [NSMutableArray arrayWithObjects:@"111", nil];
__weak typeof(self)weakself = self;
_strblk = ^NSString *(NSString * str){
return [NSString stringWithFormat:@"%@", weakself.array[0]];
};
_strblk(@"hello");
}
創(chuàng)建一個(gè)弱引用指針指向_array,這樣在block中,使用的就是弱引用指針,當(dāng)前對(duì)象釋放后,弱引用也就會(huì)自動(dòng)釋放,當(dāng)前當(dāng)前對(duì)象的block也會(huì)被釋放掉。
分析下面代碼:
- (void)MyMethod{
__block MyBlock *blockself = self;
_blk = ^int(int num){
return num * blockself.var;
};
_blk(3);
}
分析
- MRC下,不會(huì)產(chǎn)生循環(huán)引用
- 在ARC下會(huì)產(chǎn)生循環(huán)引用,引起內(nèi)存泄漏
在self修飾為__block的時(shí)候,系統(tǒng)會(huì)自動(dòng)生成一個(gè)對(duì)象__Block_byref_blockself_0
而這個(gè)對(duì)象強(qiáng)持有self。最終導(dǎo)致了,當(dāng)前對(duì)象持有block,block持有__block對(duì)象,__block對(duì)象對(duì)象持有self造成大環(huán)引用。
ARC下的解決方案

斷環(huán),當(dāng)我block中的__block使用完成以后,需要直接賦值為nil這樣就可以斷環(huán)了。
- (void)MyMethod{
__block MyBlock *blockself = self;
_blk = ^int(int num){
int = num * blockself.var;
blockself = nil;
return num * blockself.var;
};
_blk(3);
}
BLock面試題
什么是block?
block就是一個(gè)對(duì)象,這個(gè)對(duì)象封裝了函數(shù)以及其上下文。
為什么block會(huì)產(chǎn)生循環(huán)引用?
如果當(dāng)前block對(duì)當(dāng)前對(duì)象的成員變量有一個(gè)強(qiáng)引用,對(duì)象的成員變量歸根究底的還是需要通過self去訪問它,那么就是當(dāng)前對(duì)象強(qiáng)引用了blcok,而block中又調(diào)用了self并將其強(qiáng)引用,這樣就造成了自循環(huán)引用,解決方案使用__weak修飾你的成員變量,或者修飾當(dāng)前對(duì)象。在block直接調(diào)用weak變量,
大環(huán)引用,當(dāng)使用__block修飾self,并且self強(qiáng)引用了block。這樣就造成了大環(huán)引用。
self強(qiáng)持有block,block強(qiáng)持有__block對(duì)象,__block對(duì)象強(qiáng)持有了self,這就是大環(huán)循環(huán)引用。解決方案就是使用斷環(huán)的方式,但是有一個(gè)缺點(diǎn)就是,如果這個(gè)block不去執(zhí)行的話永遠(yuǎn)不會(huì)解除這個(gè)循環(huán)引用。將__block修飾的self在block中使用完成置nil。
怎么樣理解Block截獲變量的特性?
- 基本數(shù)據(jù)局部變量
- 值
- 對(duì)象類型局部變量
- 對(duì)其持有一個(gè)強(qiáng)引用,連同它的所有權(quán)
- 對(duì)于靜態(tài)局部變量
- 對(duì)其指針進(jìn)行截獲
- 靜態(tài)全局變量、全局變量
- 不產(chǎn)生截獲的
你都遇到過哪些循環(huán)引用?你又是如何解決的?
block捕獲的變量是當(dāng)前的成員變量,block也是當(dāng)前對(duì)象的成員變量,就會(huì)造成自循環(huán)引用,可以避免這種情況,對(duì)當(dāng)前當(dāng)前成員變量使用__weak所有權(quán)修飾符去解除
__block的情況也會(huì)造成循環(huán)引用,這種情況跟自循環(huán)引用不太一樣,大環(huán)循環(huán)引用有個(gè)弊端就是當(dāng)你在不調(diào)用block的情況下,這個(gè)循環(huán)引用會(huì)一直解不開。