1. 程式人生 > >深入探索Block(一)

深入探索Block(一)

目錄

一 ,Block的本質

 二,Block的變數捕獲

三,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。

總結:block3種類型,可以通過呼叫class方法或者isa指標檢視具體型別,最終都是繼承自NSBlock型別

 

  1. __NSGlobalBlock__ _NSConcreteGlobalBlock
  2. __NSStackBlock__ _NSConcreteStackBlock
  3. __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;
}