02 - Block(基礎(chǔ)篇)

block,一個用的熟的不能再熟的東東。但是,被問起block是怎么實(shí)現(xiàn)的,為什么要使用___weak,為什么會造成循環(huán)引用,__block怎么用,block分為幾種。。。我就傻眼了。這次,就好好來分析一下,從本質(zhì)出發(fā)~

1 - Block的聲明以及實(shí)現(xiàn)

首先block的申明 (返回?cái)?shù)據(jù)類型)(^block名稱)(參數(shù));
如下代碼:

@property (copy , nonatomic) void(^blockName)(void);
// 或者如下
void(^tempHandle)(void);

block的實(shí)現(xiàn) ^返回?cái)?shù)據(jù)類型(參數(shù)){具體部分}
如下代碼:

^int(int b) {
            return 1;
  };
// 省略返回值和參數(shù)代碼如下
^ {
            return 1;
  };

2 - Block的本質(zhì)

根據(jù)之前我們學(xué)過的方法,我們先創(chuàng)建一個簡單的包含block的代碼,如下。

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        bool(^gloadBlock)(int a) = ^bool(int b){
            NSLog(@"--- block");
            return false;
        };
        
        NSLog(@"%d",gloadBlock(1));
    }
    return 0;
}

使用命令指令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp得到對應(yīng)的cpp文件,main.cpp。
main.cpp中包含了以下代碼


關(guān)于main函數(shù)如下:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        bool(*gloadBlock)(int a) = ((bool (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_swf2m0ds52l_g_ly5nz8vxmw0000gn_T_main_24b9e7_mi_1,((bool (*)(__block_impl *, int))((__block_impl *)gloadBlock)->FuncPtr)((__block_impl *)gloadBlock, 1));
    }
    return 0;
}

關(guān)于__main_block_impl_0的數(shù)據(jù)結(jié)構(gòu):

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

關(guān)于__block_impl的數(shù)據(jù)結(jié)構(gòu):

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

關(guān)于__main_block_desc_0的數(shù)據(jù)結(jié)構(gòu):

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

來總結(jié)一下上面的結(jié)論。
block,就是一個結(jié)構(gòu)體指針(也是一個oc的對象),它的結(jié)構(gòu)體包含有兩個屬性implDesc。
impl是一個結(jié)構(gòu)體__block_impl,它包含有4個屬性,如下表格:

屬性名 內(nèi)容
isa 一個isa指針,關(guān)于isa指針,參照第一篇文章
Flags FIXME: 這里未完成
Reserved FIXME: 這里未完成
FuncPtr block包含函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境

3 - Block的變量捕獲

我們再來看下面的這個block代碼:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        int intA = 123;
        bool(^gloadBlock)(int a) = ^bool(int b){
            NSLog(@"--- %d",intA);
            return false;
        };
        intA = 321;

        NSLog(@"%d",gloadBlock(1));
    }
    return 0;
}

對于這個block,輸出結(jié)果是

2021-02-08 17:20:49.357112+0800 Block_01[29634:735842] --- 123
2021-02-08 17:20:49.357532+0800 Block_01[29634:735842] 0
Program ended with exit code: 0

這個結(jié)果,也許和你想的不太一樣。為什么不是--- 321。
再比較下下面的代碼:


int intA;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        intA = 123;
        bool(^gloadBlock)(int a) = ^bool(int b){
            NSLog(@"--- %d",intA);
            return false;
        };
        intA = 321;

        NSLog(@"%d",gloadBlock(1));
    }
    return 0;
}

輸出結(jié)果是

2021-02-08 17:25:36.922729+0800 Block_01[29653:738966] --- 321
2021-02-08 17:25:36.923093+0800 Block_01[29653:738966] 0
Program ended with exit code: 0

對于這樣的問題。我們應(yīng)該分析下底層代碼來找尋答案。


使用命令導(dǎo)出c++的代碼分別如下:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int intA = 123;
        bool(*gloadBlock)(int a) = ((bool (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, intA));
        intA = 321;

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_swf2m0ds52l_g_ly5nz8vxmw0000gn_T_main_470d12_mi_1,((bool (*)(__block_impl *, int))((__block_impl *)gloadBlock)->FuncPtr)((__block_impl *)gloadBlock, 1));
    }
    return 0;
}
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        intA = 123;
        bool(*gloadBlock)(int a) = ((bool (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        intA = 321;

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_swf2m0ds52l_g_ly5nz8vxmw0000gn_T_main_aec0d3_mi_1,((bool (*)(__block_impl *, int))((__block_impl *)gloadBlock)->FuncPtr)((__block_impl *)gloadBlock, 1));
    }
    return 0;
}

注意這句代碼:

        bool(*gloadBlock)(int a) = ((bool (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, intA));

__main_block_impl_0構(gòu)造函數(shù)后面多了一個參數(shù)intA。

再這個結(jié)構(gòu)體__main_block_impl_0

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int intA;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _intA, int flags=0) : intA(_intA) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

該結(jié)構(gòu)體中多出了 int intA這個屬性,并且構(gòu)造函數(shù)intA(_intA)可知,再創(chuàng)建這個block的時候,將在內(nèi)部創(chuàng)建一個intA屬性,將block外部intA的值傳入結(jié)構(gòu)體中。所以,在block之外改變intA的值,并不會改變block內(nèi)部的intA的值。

而將intA放入全局變量的話,block中并不會存在intA屬性。


這就是block的變量捕獲~

我們先來看一下block的變量捕獲。

變量類型 是否捕獲 訪問方式
局部變量(auto) ? 值傳遞
局部變量(static) ? 指針傳遞
全局變量 ? 直接訪問

我們來校驗(yàn)一下上面的結(jié)論,運(yùn)行以下代碼:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        static int intA = 123;
        auto int intB = 123;
        bool(^gloadBlock)(int a) = ^bool(int b){
            NSLog(@"--- %d,%d",intA,intB);
            return false;
        };
        intA = 321;
        intB = 321;

        NSLog(@"%d",gloadBlock(1));
    }
    return 0;
}

輸出結(jié)果:

--- 321,123
0

我們再來看一下轉(zhuǎn)換成c++的代碼吧。

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        static int intA = 123;
        auto int intB = 123;
        bool(*gloadBlock)(int a) = ((bool (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &intA, intB));
        intA = 321;
        intB = 321;

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_swf2m0ds52l_g_ly5nz8vxmw0000gn_T_main_dfee55_mi_1,((bool (*)(__block_impl *, int))((__block_impl *)gloadBlock)->FuncPtr)((__block_impl *)gloadBlock, 1));
    }
    return 0;
}

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *intA;
  int intB;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_intA, int _intB, int flags=0) : intA(_intA), intB(_intB) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

當(dāng)局部變量是auto修飾的話(默認(rèn)為auto),block會進(jìn)行值捕獲。當(dāng)局部變量是static修飾的話,block會進(jìn)行指針捕獲。self屬于auto修飾的局部變量。


4 - Block的類型

先看一下下面的代碼:

#import <Foundation/Foundation.h>

void (^gloadBlockC)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        static int intA = 123;
        auto int intB = 123;
        
        // GlobalBlock
        void(^globalBlock)(int a) = ^void(int b){
            NSLog(@"--- %d,",b);
        };
        void(^globalBlockB)(void) = ^void(){
            NSLog(@"--- %d,",intA);
        };
        gloadBlockC = ^void(){
            
        };

        NSLog(@"%@",[globalBlock class]);
        NSLog(@"%@",[globalBlockB class]);
        NSLog(@"%@",[gloadBlockC class]);
        NSLog(@"%@",[^void(){
        } class]);
        
        // StackBlock
        NSLog(@"%@",[^void(){
            NSLog(@"%d",intB);
        } class]);

        // MallocBlock
        void(^mallocBlock)(void) = ^void(){
            NSLog(@"--- %d,",intB);
        };
        NSLog(@"%@",[mallocBlock class]);
    }
    return 0;
}

在ARC環(huán)境下運(yùn)行結(jié)果為:

NSGlobalBlock
NSGlobalBlock
NSGlobalBlock
NSGlobalBlock
NSStackBlock
NSMallocBlock

在MRC環(huán)境下運(yùn)行結(jié)果為:

NSGlobalBlock
NSGlobalBlock
NSGlobalBlock
NSGlobalBlock
NSStackBlock
NSStackBlock

所以,block有3種類型:global、stack、malloc。
為什么第六個block類型在ARC和MRC會不一樣?經(jīng)過測試驗(yàn)證,將__NSStackBlock__做copy后,得到的就是一個__NSMallocBlock__.所以,應(yīng)該是ARC環(huán)境下,NSStackBlock的賦值會自動加入copy操作造成的。

我們總結(jié)一下:

block類型 產(chǎn)生的環(huán)境 存儲區(qū)域 copy之后的效果
GlobalBlock block沒有訪問auto變量 數(shù)據(jù)區(qū)域 類型不變
StackBlock block訪問了auto變量 從棧賦值到堆
MallocBlock StackBlock進(jìn)行了copy 引用計(jì)數(shù)+1

經(jīng)查詢資料,補(bǔ)充一個知識點(diǎn):

在ARC環(huán)境下,編譯器會根據(jù)情況自動將棧上的block復(fù)制到堆上,比如以下情況:

  • block作為函數(shù)返回值時
  • 將block賦值給__strong指針時
  • block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時
  • block作為GCD API的方法參數(shù)時

為了自己來管理block的釋放,我們應(yīng)該將屬性的block放入堆中。所以,在ARC下,利用上述第二條特性,block用strong或者copy來修飾。而在MRC下,block就用copy來修飾。


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

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

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