iOS晉級(jí)知識(shí)匯總(九)Block

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)一起截獲
  • 靜態(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.

棧的__Block修飾的變量.png

__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的內(nèi)存分布.png

Block的copy操作

Block的copy操作.png

聲明一個(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的copy操作.png

棧上的Block的銷毀

棧上,等作用域

棧block的銷毀.png

棧上__block變量的copy

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

棧的__Block修飾的變量.png

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)循環(huán)引用.png

斷環(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ì)一直解不開。

最后編輯于
?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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