探尋Block的本質(1)—— 基本認識

Block傳送門🦋🦋🦋

探尋Block的本質(2)—— 底層結構面試

探尋Block的本質(3)—— 基礎類型的變量捕獲sass

探尋Block的本質(4)—— Block的類型markdown

探尋Block的本質(5)—— 對象類型的變量捕獲iphone

探尋Block的本質(6)—— __block的深刻分析函數

block是什麼

通俗的理解:block就是將一些代碼封裝起來,以便在未來某個時候被使用,若是你不去調用block,block內部封裝的代碼就不會執行。舉一個簡單的例子,下面在main函數中定義一個最簡單的blockpost

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ^{
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
        }; 
    return 0;
}

**********************   運行結果   ************************
Program ended with exit code: 0
複製代碼

運行程序運行能夠看到block內的代碼是沒有運行的,由於沒有調用。Block的使用也很簡單,能夠像函數同樣被使用。加上()就表明調用,以下ui

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ^{
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
        }(); 
    return 0;
}

**********************   運行結果   ************************
2019-05-28 17:18:56.992746+0800 Interview03-block[2640:180864] I am a block!
2019-05-28 17:18:56.992924+0800 Interview03-block[2640:180864] I am a block!
2019-05-28 17:18:56.992939+0800 Interview03-block[2640:180864] I am a block!
2019-05-28 17:18:56.992947+0800 Interview03-block[2640:180864] I am a block!
Program ended with exit code: 0
複製代碼

若是上面寫的太簡練不習慣的話,一般你們多是這麼寫編碼

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) = ^{
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
        };
        block();

**********************   運行結果   ************************
2019-05-28 17:18:56.992746+0800 Interview03-block[2640:180864] I am a block!
2019-05-28 17:18:56.992924+0800 Interview03-block[2640:180864] I am a block!
2019-05-28 17:18:56.992939+0800 Interview03-block[2640:180864] I am a block!
2019-05-28 17:18:56.992947+0800 Interview03-block[2640:180864] I am a block!
Program ended with exit code: 0
複製代碼

至於block的書寫語法請自行搞定。spa

你可能在面試中被這麼問過:Block的本質是什麼?命令行

顯然,僅僅經過上面的知識,確定不會讓面試官滿意的。這裏介紹一張block的底層結構圖 關於此圖有以下二點解釋:

  • block的本質也是一個OC對象,它內部也有個isa指針
  • block是封裝了函數調用函數調用環境的OC對象

第一點很容易看出,block地層結構圖中的第一個成員就是一個isa指針,因此咱們能夠將block當成一個對象來看待。那麼第二點中的 函數調用函數調用環境是什麼意思呢?咱們一步一步來。 咱們先將上面的代碼擴展一下

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        void (^block)(int, int) = ^(int c, int b){
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
            NSLog(@"c = %d",c);
            NSLog(@"b = %d",b);
            NSLog(@"a的值爲%d",a);
        };
        block(50,100);
    }
    return 0;
}

**********************   運行結果   ************************
2019-05-29 17:13:56.021422+0800 Interview03-block[9455:836127] I am a block!
2019-05-29 17:13:56.021630+0800 Interview03-block[9455:836127] I am a block!
2019-05-29 17:13:56.021639+0800 Interview03-block[9455:836127] c = 50
2019-05-29 17:13:56.021649+0800 Interview03-block[9455:836127] b = 100
2019-05-29 17:13:56.021700+0800 Interview03-block[9455:836127] a的值爲10
Program ended with exit code: 0
複製代碼

上面的代碼能夠看出,block裏面使用了它上面的 int a = 10 ,能夠將這個先簡單的理解成函數調用環境,顧名思義,就是block所用到的一些外部變量。 函數調用指的就是上面包含那5句打印代碼的匿名函數,block被調用的時候,該函數就會被調用。 接下來,咱們在命令行中,使用

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
複製代碼

將main.m文件轉換成C++中間代碼,來仔細研究一下block的底層構造。在main.cpp文件中,將代碼直接拉到底部,能夠發現咱們須要的內容。核心代碼整理後以下,下面的代碼展現了運行狀態下block內的實際內容

#import <Foundation/Foundation.h>

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

struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int a;
    
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        
        int a = 10;
        
        void (^block)(int, int) = ^(int c, int b){
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
            NSLog(@"c = %d",c);
            NSLog(@"b = %d",b);
            NSLog(@"a的值爲%d",a);
            
        };
        
        struct __main_block_impl_0 *tmpBlock = (__bridge struct __main_block_impl_0 *)block;
        
        block(50,100);
        

    }
    return 0;
}
複製代碼

咱們將block的底層結構struct __main_block_impl_0直接般到main.m裏面,同時,還須要將struct __block_impl以及struct __main_block_desc_0也搬過來,以保證編譯正確,這樣,咱們即可以經過struct __main_block_impl_0來讀取運行時下,block中的內容

struct __main_block_impl_0 *tmpBlock = (__bridge struct __main_block_impl_0 *)block;

咱們能夠在調試窗口看到以下信息運行時下block內部的信息 接下來咱們在block內部的代碼段加上斷點 而後經過Debug-->DebugWorkflow-->Always Show Disassembly查看一下此時的彙編代碼

Interview03-block`__main_block_invoke:
    0x100000e90 <+0>:   pushq  %rbp
    0x100000e91 <+1>:   movq   %rsp, %rbp
    0x100000e94 <+4>:   subq   $0x20, %rsp
    0x100000e98 <+8>:   leaq   0x1c1(%rip), %rax         ; @"I am a block!"
    0x100000e9f <+15>:  movq   %rdi, -0x8(%rbp)
    0x100000ea3 <+19>:  movq   %rdi, %rcx
    0x100000ea6 <+22>:  movl   %esi, -0xc(%rbp)
    0x100000ea9 <+25>:  movl   %edx, -0x10(%rbp)
    0x100000eac <+28>:  movq   %rcx, -0x18(%rbp)
->  0x100000eb0 <+32>:  movq   %rdi, -0x20(%rbp)
    0x100000eb4 <+36>:  movq   %rax, %rdi
    0x100000eb7 <+39>:  movb   $0x0, %al
    0x100000eb9 <+41>:  callq  0x100000f16               ; symbol stub for: NSLog
    0x100000ebe <+46>:  leaq   0x19b(%rip), %rcx         ; @"I am a block!"
    0x100000ec5 <+53>:  movq   %rcx, %rdi
    0x100000ec8 <+56>:  movb   $0x0, %al
    0x100000eca <+58>:  callq  0x100000f16               ; symbol stub for: NSLog
    0x100000ecf <+63>:  leaq   0x1aa(%rip), %rcx         ; @"c = %d"
    0x100000ed6 <+70>:  movl   -0xc(%rbp), %esi
    0x100000ed9 <+73>:  movq   %rcx, %rdi
    0x100000edc <+76>:  movb   $0x0, %al
    0x100000ede <+78>:  callq  0x100000f16               ; symbol stub for: NSLog
    0x100000ee3 <+83>:  leaq   0x1b6(%rip), %rcx         ; @"b = %d"
    0x100000eea <+90>:  movl   -0x10(%rbp), %esi
    0x100000eed <+93>:  movq   %rcx, %rdi
    0x100000ef0 <+96>:  movb   $0x0, %al
    0x100000ef2 <+98>:  callq  0x100000f16               ; symbol stub for: NSLog
    0x100000ef7 <+103>: leaq   0x1c2(%rip), %rcx         ; @
    0x100000efe <+110>: movq   -0x20(%rbp), %rdi
    0x100000f02 <+114>: movl   0x20(%rdi), %esi
    0x100000f05 <+117>: movq   %rcx, %rdi
    0x100000f08 <+120>: movb   $0x0, %al
    0x100000f0a <+122>: callq  0x100000f16               ; symbol stub for: NSLog
    0x100000f0f <+127>: addq   $0x20, %rsp
    0x100000f13 <+131>: popq   %rbp
    0x100000f14 <+132>: retq   
複製代碼

這裏不用糾結匯編具體寫了什麼,從最右邊欄的信息,咱們也能大體猜想出這段彙編碼應該就是block中所包裹的那段函數體實現,事實也確實如此,這裏咱們只須要注意第一句彙編碼最左側的0x100000e90,這個就是block中所包含的函數實現的入口地址。而剛纔咱們的截圖中block內部所包含的FuncPtr = (void *)0x100000e90,也確實驗證這個FuncPtr所指向的就是block內部所包含的那段函數實現的入口。

小結

  • block的本質是一個OC對象

由於block底層結構的第一個成員其實是一個isa指針

  • block封裝了函數調用函數調用環境

由於block的內部存儲了block函數實現的入口地址(函數調用),還捕獲了block函數所用到的外部的一些變量(函數調用環境)。

Block傳送門🦋🦋🦋

探尋Block的本質(2)—— 底層結構

探尋Block的本質(3)—— 基礎類型的變量捕獲

探尋Block的本質(4)—— Block的類型

探尋Block的本質(5)—— 對象類型的變量捕獲

探尋Block的本質(6)—— __block的深刻分析

相關文章
相關標籤/搜索