探尋Block的本質(4)—— Block的類型markdown
探尋Block的本質(5)—— 對象類型的變量捕獲iphone
探尋Block的本質(6)—— __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的底層結構圖 關於此圖有以下二點解釋:
函數調用
和函數調用環境
的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內部的代碼段加上斷點
而後經過
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函數
所用到的外部的一些變量(函數調用環境)。