深入探索Block(一)
目錄
一 ,Block的本質
.m 檔案程式碼如下
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
void (^block)(void) = ^{
NSLog(@"this is a block-%d",age);
};
age = 20;
block();
}
return 0;
}
使用終端命令 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m 檢視底層實現的檔案。
結論如下:
- Block 內部也有一個isa指標
- 封裝了函式呼叫以及函式呼叫環境內的OC物件
二,Block的變數捕獲
- 區域性變數:auto/static 修飾的區域性變數,Block 都會將其捕獲到block內部;auto修飾的區域性變數,block 通過值傳遞的方式訪問;static修飾的區域性變數,block 通過指標傳遞的方式訪問;
- 全域性變數:block 不將其捕獲,直接訪問。
- 以下舉例只是驗證了Block 捕獲的是區域性變數,至於static的捕獲,希望大家自己動手實現一下,去驗證為何 static 宣告的區域性變數在block 呼叫之前修改其值,block 內部引用的其值也跟著變。
#import "XZPerson.h" @implementation XZPerson //block 會將 _name 捕獲到block內部嗎? - (void)test{ void (^block)(void) = ^{ NSLog(@"-------------%p",self->_name); }; block(); } @end ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- /* 1.使用終端命令 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc XZPerson.m 生成c++檔案 2.block 本質程式碼如下,可以看到,block 是通過捕獲ZPerson *self,進而訪問成員變數_name. struct __XZPerson__test_block_impl_0 { struct __block_impl impl; struct __XZPerson__test_block_desc_0* Desc; XZPerson *self; __XZPerson__test_block_impl_0(void *fp, struct __XZPerson__test_block_desc_0 *desc, XZPerson *_self, int flags=0) : self(_self) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; 3.通過cpp 檔案可以看出,test的底層實現如下: static void _I_XZPerson_test(XZPerson * self, SEL _cmd) { void (*block)(void) = ((void (*)())&__XZPerson__test_block_impl_0((void *)__XZPerson__test_block_func_0, &__XZPerson__test_block_desc_0_DATA, self, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } 每個方法都有兩個隱藏的引數 self(方法呼叫者),SEL _cmd(方法的實現),兩個都是區域性變數,block會捕獲區域性變數到其內部也可從此得出結論 */
三,Block 的型別
上面提到block 是oc物件,既然是物件就能呼叫oc的class 方法,檢視其元類物件以及父類物件
void (^block)(void) = ^{
NSLog(@"this is a block");
};
NSLog(@"block-1%@",[block class]); /*結果:__NSGlobalBlock__*/
NSLog(@"block-2%@",[[block class] superclass]); /*結果:__NSGlobalBlock*/
NSLog(@"block-3%@",[[[block class] superclass] superclass]); /*結果:NSBlock*/
NSLog(@"block-4%@",[[[[block class] superclass] superclass] superclass]); /*結果:NSObject*/
通過以下方法可知道block的型別
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block1)(void) = ^{
NSLog(@"this is one block");
};
int age =10;
void (^block2)(void) = ^{
NSLog(@"this is two block-%d",age);
};
NSLog(@"%@-%@-%@",[block1 class],[block2 class],[^{
NSLog(@"this is three block-%d",age);
} class]);
//__NSGlobalBlock__ - __NSMallocBlock__ - __NSStackBlock__
}
return 0;
}
這裡大家也許有個疑問?使用終端命令 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m 檢視底層c++檔案這三個block生成的block 型別都為同一種,_NSConcreteStackBlock?這個不用奇怪,我們一切以執行時為主。clang 只是LLVM編譯器的一部分,也許LLVm在執行過程中做了一定的處理。導致執行的結果是三種不同型別的Block。
總結:block有3種類型,可以通過呼叫class方法或者isa指標檢視具體型別,最終都是繼承自NSBlock型別
- __NSGlobalBlock__ ( _NSConcreteGlobalBlock )
- __NSStackBlock__ ( _NSConcreteStackBlock )
- __NSMallocBlock__ ( _NSConcreteMallocBlock )
int main(int argc, const char * argv[]) {
@autoreleasepool {
//MRC下探索:
//沒有訪問auto變數的block 就是global型別的block;
//訪問全域性+靜態的區域性變數都是global型別的block
//訪問auto的就是stack 型別的block,注意ARC下列印的是malloc型別,如果想要探索本質,記得吧ARC關閉哦。
void (^block1)(void) = ^{
NSLog(@"this is one block");
} ;
//__NSGlobalBlock__ 呼叫copy 還是__NSGlobalBlock__
int age =10;
void (^block2)(void) = ^{
NSLog(@"this is two block-%d",age);
} ;
//MRC:__NSStackBlock__呼叫copy 變成了__NSMallocBlock__即從棧區變到了堆區
NSLog(@"%@-%@",[block1 class],[block2 class]);
}
return 0;
}