iOS block、あなたが見たいのはすべてあります.

17026 ワード

Blocks are a non-standard extension added by Apple Inc. to Clang's implementations of the C, C++, and Objective-C programming languages that uses a lambda expression-like syntax to create closures within these languages. Blocks are supported for programs developed for Mac OS X 10.6+ and iOS 4.0+,although third-party runtimes allow use on Mac OS X 10.5 and iOS 2.2+ and non-Apple systems.
wikipedia,blockを参照すると,実はC,C++,OCにおける閉パッケージの表現である.
一、block構造剖析
blockを一言で形容しなければならないなら、変数をキャプチャできる関数にたとえます.このセクションでは、特に簡単なblockで構造解析を行います.
分析:
//          block  ,  main.m  
typedef int (^Block)(void);
int main(int argc, char * argv[]) {
    // block  
    Block block = ^{
        return 0;
    };
    // block  
    block();
    return 0;
}

//     main.m       :clang -rewrite-objc main.m
//   main.cpp,    main.cpp             (      )
// block     
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
typedef int (*Block)(void);
// block   
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;
  }
};
// block    
static int __main_block_func_0(struct __main_block_impl_0 *__cself) {
        return 0;
 }
// block     
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)};
// main  
int main(int argc, char * argv[]) {
    // block  
    Block block = ((int (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    // block  
    ((int (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

では、一番下のmain関数から見ると、上のコードに対応する2つのステップに分けられます.
  • block実装:block構造体の構築関数を呼び出す_main_block_impl_0はblockを実現して、見える、伝わる2つのパラメータ、それぞれ関数です_main_block_func_0のポインタと_main_block_desc_0構造体.
  • block呼び出し:blockをパラメータとしてblockのFuncPtr、すなわち_main_block_func_0メソッド、を呼び出します.

  • 2つのステップを比較すると、2番目のステップは比較的簡単で、最初の説明にも合致し、1番目のステップのblockをパラメータとしてFucPtrに転送すると、block実装位置のコンテキストにアクセスすることができます.では、最初のステップで見なければならないのはblockの構造体です.下記のコメントをご覧ください.
    // block   
    struct __main_block_impl_0 {
      // impl   , block     
      struct __block_impl impl;
      // Desc   , block      
      struct __main_block_desc_0* Desc;
      // block        
      __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;
      }
    };
    
    // impl   
    struct __block_impl {
      void *isa;  //     ,_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock
      int Flags;  //      block      
      int Reserved;  //     
      void *FuncPtr;  //     ,   Block       , __main_block_func_0
    };
    
    // Desc   
    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)};
    

    まとめ:
    以上から分かるように、blockは関数よりも2つのステップを多く使用している.すなわち、構造関数を使用してFuncPtrを実装し、使用する場合、block構造体に転送する必要がある.blockは関数よりも多くの機能を有し,実装された場所で上記にアクセスできると結論した.
    質問:
    1、blockが変数をキャプチャする方法については言及していないようです.2、isaが示す3種類の記憶位置_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlockの関係は何ですか.
    二、変数のキャプチャ
    第1部では,2つの疑問を残したが,本節ではまず変数のキャプチャ問題を見た.ここでは,変数のblockをキャプチャする構造体解析とblockが変数をキャプチャするタイミングという2つのキーがある.
    1、変数をキャプチャするblock構造体実現
    // block        
    typedef int (^Block)(void);
    int main(int argc, char * argv[]) {
        int i = 0;
        Block block = ^{
            return i;
        };
        block();
        return 0;
    }
    
    //     main.m       :clang -rewrite-objc main.m
    //   main.cpp,    main.cpp block     main  
    // block   
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int i;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    // main  
    int main(int argc, char * argv[]) {
        int i = 0;
        Block block = ((int (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));
        ((int (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        return 0;
    }
    

    変数なしでキャプチャされたサンプルとは2つの違いがあります.
  • block構造体に変数iが1つ増えたのも、なぜblockが変数をキャプチャするのか.iの値がどのように初期化されるかについては、以下を参照してください.
  • blockのコンストラクション関数_main_block_impl_0には1つのパラメータiと:i(_i)が追加され、パラメータiはiに値を割り当てるために理解され、i(_i)はc++構文構造体におけるconstタイプ変数の初期化方式であり、パラメータ_i block構造体の変数iに付与する.

  • 2、blockが変数をキャプチャするタイミング
    2.1を参照して、main関数の実装:((int(*))&_main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i)).ブロックは構造の時点でパラメータiを構造体に伝達して初期化するので,ブロックは実現場所で変数をキャプチャし,キャプチャした変数の値も実現時点の変数値であることがわかる.
    三、変数のタイプを切り取る
    公式文書を参照:
    1、Global variables are accessible, including static variables that exist within the enclosing lexical scope. 2、Parameters passed to the block are accessible (just like parameters to a function). 3、Stack (non-static) variables local to the enclosing lexical scope are captured as const variables.Their values are taken at the point of the block expression within the program. In nested blocks, the value is captured from the nearest enclosing scope. 4、Variables local to the enclosing lexical scope declared with the _block storage modifier are provided by reference and so are mutable.Any changes are reflected in the enclosing lexical scope, including any other blocks defined within the same enclosing lexical scope. These are discussed in more detail in The _block Storage Type. 5、Local variables declared within the lexical scope of the block, which behave exactly like local variables in a function.Each invocation of the block provides a new copy of that variable. These variables can in turn be used as const or by-reference variables in blocks enclosed within the block.
    block可の外部変数は、グローバル変数、グローバル静的変数、ローカル静的変数の順に5種類あることがわかります.block修飾変数とローカル変数(キャプチャ後はconstタイプ).ここでは、まず_blockを削除し、他の4つを見てみましょう.
    // block        
    #import 
    typedef int (^Block)(void);
    int a = 0;
    static int b = 0;
    int main(int argc, char * argv[]) {
        static int c = 0;
        int i = 0;
        NSMutableArray *arr = [NSMutableArray array];
        Block block = ^{
            a = 1;
            b = 1;
            c = 1;
            [arr addObject:@"1"];
            return i;
        };
        block();
        return 0;
    }
    

    ここで問題があります.clang-rewrite-objc mainを引き続き使用すると.OCライブラリUIKEtが含まれているので、xcrun-sdk iphonesimulator 9を使用します.3 clang -rewrite-objc main.m、詳しく知りたいならclang-rewrite-objcの使用点滴を見てください.
    // block        
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int *c;
      NSMutableArray *arr;
      int i;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_c, NSMutableArray *_arr, int _i, int flags=0) : c(_c), arr(_arr), i(_i) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    // block     
    static int __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int *c = __cself->c; // bound by copy
      NSMutableArray *arr = __cself->arr; // bound by copy
      int i = __cself->i; // bound by copy
      a = 1;
      b = 1;
      (*c) = 1;
      ((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)arr, sel_registerName("addObject:"), (id)(NSString *)&__NSConstantStringImpl__var_folders_k__6b9p9yt96y9dq8ds8_kvf3kh0000gn_T_main_3c9752_mi_0);
      return i;
    }
    // mian  
    int main(int argc, char * argv[]) {
        static int c = 0;
        int i = 0;
        NSMutableArray *arr = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
        Block block = ((int (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &c, arr, i, 570425344));
        ((int (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        return 0;
    }
    

    上記の例には全部で5種類の外部変数があり,ここでは3種類に分けられる.1、グローバル変数a、および静的グローバル変数bは第1クラスであり、blockは直接修正することができ、何も言うことはない.2、局所静的変数c、blockコンストラクション関数から、ここでcのポインタが伝わっていることがわかるので、cを変更することができます.3、局部基本変数i、局部オブジェクトarr、iは修正できないことを発見することができて、arrはオブジェクトであるため、オブジェクトを操作できます(ただしarrに他のアドレスを再び指し示すことはできません)、この場合注意しなければならないのは、block内部のiとarrはいずれもconstタイプなので、修正することはできません.本節の後、キャプチャ変数には2つの難点しかありません.すなわち、_block修飾の変数がどういう意味なのか、上記の3番目のタイプの変数に対して、私たちはどのようにblockで修正するのか.賢い子供靴は考えるべきで、2つの難点は実は1つの問題です:blockはblockでconst変数サービスを変更することを解決するためです.次の部分を見てください.
    四、ブロック作用
    3には、constタイプの変数をどのように修正するかという疑問点が残っています.実際には、ローカル静的変数とローカルオブジェクト[arr addObject:@“1”]から、変数を直接操作するのではなく、ポインタを操作することで、ポインタを新しいアドレスに向けることはできませんが、ポインタ空間を操作することができます.
    //   _block   
    #import 
    typedef int (^Block)(void);
    int main(int argc, char * argv[]) {
        __block int i = 2;
        __block NSMutableArray *arr = [NSMutableArray array];
        Block block = ^{
            i = 1;
            arr = [NSMutableArray array];
            return i;
        };
        block();
        return 0;
    }
    
    //   xcrun -sdk iphonesimulator9.3 clang -rewrite-objc main.m   
    // __block   i      ,    i i  ,forwarding        
    struct __Block_byref_i_0 {
      void *__isa;
    __Block_byref_i_0 *__forwarding;
     int __flags;
     int __size;
     int i;
    };
    // __block   arr      ,    arr arr  ,forwarding        
    struct __Block_byref_arr_1 {
      void *__isa;
      __Block_byref_arr_1 *__forwarding;
      int __flags;
      int __size;
      void (*__Block_byref_id_object_copy)(void*, void*);
      void (*__Block_byref_id_object_dispose)(void*);
      NSMutableArray *arr;
    };
    // block   
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_i_0 *i; // by ref
      __Block_byref_arr_1 *arr; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,                __Block_byref_i_0 *_i, __Block_byref_arr_1 *_arr, int flags=0) : i(_i->__forwarding), arr(_arr->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    // block    
    static int __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_i_0 *i = __cself->i; // bound by ref
      __Block_byref_arr_1 *arr = __cself->arr; // bound by ref
      (i->__forwarding->i) = 1;
      (arr->__forwarding->arr) = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
      return (i->__forwarding->i); 
    }
    // main  
    int main(int argc, char * argv[]) {
        __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 2};
        __attribute__((__blocks__(byref))) __Block_byref_arr_1 arr = {(void*)0,(__Block_byref_arr_1 *)&arr, 33554432, sizeof(__Block_byref_arr_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"))};
        Block block = ((int (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, (__Block_byref_arr_1 *)&arr, 570425344));
        ((int (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        return 0;
    }
    

    上記のコードから、基本変数iもオブジェクトarrも_blockは構造体になり、その値が構造体のメンバー変数であると、なぜblockで修正できるのかがわかります.もちろん、ここでは大きな疑問があります.つまり、簡単な構造体はblockの中で外部変数を修正する要求を完成することができますが、ここではなぜ構造体自身を指すことが多いのでしょうか.forwardingポインタは?次の部分を見てください.
    //              ,     i   ,      ,__block __forwarding         (i   3)
    #import 
    typedef int (^Block)(void);
    int main(int argc, char * argv[]) {
        int i = 1;
        int *a = &i;
        Block block = ^{
            (*a)++;
            return 0;
        };
        i ++;
        block();
        NSLog(@"%d", i);
    }
    

    五、blockのストレージタイプ
    2、3、4の節では、1で提案されたキャプチャ変数の問題(1つの_forwardingポインタの問題、この節の問題)を基本的に解くと、isaが表す3つの記憶位置_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlockの間にどのような関係があるかという問題が、この節から1で残されたもう1つの問題を解くことになる.
    1、_NSConcreteGlobalBlock
    // a、       block
    // clang      isa   _NSConcreteGlobalBlock
    #import 
    typedef int (^Block)(void);
    Block globalBlock = ^{
        return 0;
    };
    int main(int argc, char * argv[]) {
        globalBlock();
        return 0;
    }
    
    // b、  block       
    // clang      isa   _NSConcreteStackBlock,     clang       ,    ,    NSLog  。  NSLog     _NSConcreteGlobalBlock。
    #import 
    typedef int (^Block)(void);
    int main(int argc, char * argv[]) {
        Block globalBlock = ^ {
            return 0;
        };
        globalBlock();
        NSLog(@"%@", globalBlock);
        return 0;
    }
    

    2、_NSConcreteStackBlock
    ARC環境では、コンパイラは通常、スタックに作成されたblockをスタックに自動的にコピーします.blockがメソッドまたは関数のパラメータとして渡される場合にのみ、コンパイラはcopyメソッドを自動的に呼び出すことはありません.
    ブロックが、USingBlockのCocoa FrameworkメソッドまたはGCDのAPIを持つメソッド名にパラメータとして入力されるとき.これらのメソッドは、内部で渡されたblockに対してcopyまたは_を呼び出します.Block_コピーをコピーします.
    //               ,  Block block = [arr objectAtIndex:1]; crash,  getBlockArray  block     ,     。
    //                   block,        ,     ,   。
    //     NSLog   block  _NSConcreteStackBlock。
    #import 
    typedef int (^Block)(void);
    id getBlockArray()
    {
        int val = 10;
        NSLog(@"%@", ^{NSLog(@"blklog:%d", val);});
        return [[NSArray alloc] initWithObjects:
                ^{NSLog(@"blk0:%d", val);},
                ^{NSLog(@"blk1:%d", val);}, nil];
    }
    int main(int argc, char * argv[]) {
        id arr = getBlockArray();
        Block block = [arr objectAtIndex:1];
        block();
        return 0;
    }
    //     ,     
    #import 
    typedef int (^Block)(void);
    id getBlockArray()
    {
        int val = 10;
        NSLog(@"%@", ^{NSLog(@"blklog:%d", val);});
        return [[NSArray alloc] initWithObjects:
                [^{NSLog(@"blk0:%d", val) ;} copy],
                [^{NSLog(@"blk1:%d", val);} copy], nil];
    }
    int main(int argc, char * argv[]) {
        id arr = getBlockArray();
        Block block = [arr objectAtIndex:1];
        block();
        return 0;
    }
    

    3、_NSConcreteMallocBlock
    ここでは、上記のいくつかのblockを除いて、残りのblockはARC環境では_NSConcreteMallocBlockタイプ.顔を殴ることを歓迎して、それでは私はもっと多くの知識を学ぶことができます.ははは、冗談です.よく見ると、一から五までのclang情報の中で、サンプルのblockは基本的に_NSConcreteStackBlock、実はそうではありません.NSConcreteMallocBlockは、NSLog方式で検証できます.
    3つのストレージタイプは、clangコードではなくNSLogで印刷されます.
    4、_block、_forwardingとスタック
    arc環境では、スタック上のblockが自動的にスタックにcopyされ、blockが切り取られることが知られています.block変数も同時にcopyがスタックに格納されます(これは詳しくは言わないが、ライオンの本を読むことができます).これにより、キャプチャされた変数が役割ドメインを果たしてもblockがアクセスできることが保証され、4で最後に述べた方法では保証されません.以下の例です.
    #import 
    typedef int (^Block)(void);
    //       block
    Block globalBlock;
    void test() {
        int i = 1;
        int *a = &i;
        Block block = ^{
            (*a)++;
            NSLog(@"%d", *a);
            return 0;
        };
        i++;
        block();
        //    block   
        globalBlock = block;
    }
    int main(int argc, char * argv[]) {
        test();
        globalBlock();
    }
    //            
    **2016-12-15 17:30:21.188 block[8683:10811473] 3**
    **2016-12-15 17:30:21.189 block[8683:10811473] 4**
    //      
    **2016-12-15 17:30:21.188 block[8683:10811473] 3**
    **2016-12-15 17:30:21.189 block[8683:10811473] 24705**
    

    なぜ、このような結果が出るのか、*aは局所変数iを指し、iはtestメソッドがドメインに作用した後にメモリを回収するので、どこから来たのか分からない数が現れる.では_blockはこのような状況を処理できますか?
    #import 
    typedef int (^Block)(void);
    Block globalBlock;
    void test() {
        __block int i = 1;
        Block block = ^{
            i ++;
            NSLog(@"%d", i);
            return 0;
        };
        i ++;
        block();
        globalBlock = block;
    }
    int main(int argc, char * argv[]) {
        test();
        globalBlock();
    }
    //   
    **2016-12-15 17:34:07.092 block[9046:10815940] 3**
    **2016-12-15 17:34:07.094 block[9046:10815940] 4**
    

    完璧です.ここで四中を見てみましょう.block構造体の構造関数
    //       ,     i arr        _forwarding      ,    block         ,    block      copy   ,   _block  。
    //     clang       ,  block  ,  i  arr     i->__forwarding->i arr->__forwarding->arr。    block            (         )。
    __Block_byref_i_0 *_i, __Block_byref_arr_1 *_arr, int flags=0) : i(_i->__forwarding), arr(_arr->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
    

    六、循環参照
    文章はこれで終わりです.blockが変数をキャプチャすることをあまり知らないはずです.つまり、変数を持っているので、循環引用を引き起こす可能性があります.循環引用については、私はかつて文章を書いたことがあります.比較的全面的に話しています.循環引用を参照してください.私を見てください.
    七、参考文献
    1、獅子書:Objective-C高級プログラミングiOSとOS Xマルチスレッドとメモリ管理2、https://www.zybuluo.com/MicroCai/note/51116 3、http://blog.tingyun.com/web/article/detail/845 4、https://en.wikipedia.org/wiki/Blocks_(C_language_extension)
    文中部分_blockはすべて_と書きましたblock、気にしないで.お役に立てば、好きになって、注目して、ありがとうございます~~