Objective-C block、weakself、strongself実現原理を深く分析する

13498 ワード

Blockは私たちが日常のOC符号化でよく使う特性であり、非常に便利で効率的にコードを作成し、組織することができ、非同期呼び出しのコードをより精錬し、読みやすくすることができます.しかし、日常開発の過程で私たちの大部分の状況は教科書の一般的なコードを書いて符号化の正確さを確保しています.次に、blockのソースコード分析を通じてblockの実現原理を見てみましょう.文章は少し長いので、辛抱強くしてください.きっと収穫があると信じています.
通常、blockを使用すると、次のコードが書かれます.
- (void)function{
  __weak typeof(self) weakself = self; // 
  [teseObject callFunc:^{
    __strong typeof(self) strongself = weakself; //block 
    [strongself callFunc_1:....];
    [strongself callFunc_2:....];
    ....
}];
}

次に、問題を持って、上のコードを分析します.1.selfを直接使ってもいいですか?いけません.このようにblockはselfオブジェクトを強く持ち、ループリファレンスをもたらし、メモリが漏洩するためです.
2.weakselfを直接使ってもいいですか?状況を見る.Weakselfはオブジェクトを持たないため、ループリファレンスの問題は発生しませんが、weakselfを使用するとblockの実行が不一致になる問題があります.上記のコードを考えてみてください.「callFunc_1」を呼び出すとweakselfは有効ですが、「callFunc_2」を呼び出すとweakselfはすでにnilである可能性があります.これにより、block内の実行が不一致になり、予想外の結果になります.
3.循環引用は悪人ばかりではないか.答えは否定的だ.blockが実行を開始するとstrongselfはselfオブジェクトの値を取りに行き、selfがnilである場合、strongselfはblock実行中全体がnilであり、selfが有効である場合、strongselfはループリファレンスの特性を利用してblock実行時にselfオブジェクトが析出しないことを保証し、block実行の一貫性を保証する.実は私たちはビジネスコードを書くとき(多くのサードパーティのオープンソースクラスライブラリ)にループリファレンスという特性を利用して、blockで参照したオブジェクトがblock実行時に依然として有効であることを保証しますが、このような黒い魔法を使うときはblock実行が終わった後にループリファレンスを破ることは避けましょう.strongselfはblock内部で定義された変数であるため、block実行が終了するとシステムによって回収され、循環参照が破られる
まとめ:strongselfを使用すると、ループリファレンスによるメモリの漏洩を解消したり、blockの実行中の一貫性を保証したりすることができます.だから正常なビジネスコードは私たちが上の標準的な方法で書くことができて、特殊な情況だけがblockの特性を利用していくつかの黒い魔法のコードを書くことができます.
次に、blockのソースコードを解読してblockが何をしたのかを見てみましょう.まず、面接問題のようなものを見てみましょう.
        NSInteger count = 10;
        NSInteger(^sum)(void)=^{
            return count;
        };
        count = 20;
        NSLog(@"\
%ld",sum()); // :10 __block NSInteger block_count = 10; NSInteger(^block_sum)(void)=^{ return block_count; }; block_count = 20; NSLog(@"\
%ld",block_sum()); // :20 NSMutableString *mutable_string = [NSMutableString stringWithString:@"aaa"]; void(^mutable_append)(void)=^{ [mutable_string appendString:@"ccc"]; }; [mutable_string appendString:@"bbb"]; mutable_append(); NSLog(@"\
%@",mutable_string); // :aaabbbccc NSString *string = @"aaa"; NSString*(^append)(void)=^{ return [string stringByAppendingString:@"ccc"]; }; string = @"bbb"; NSLog(@"\
%@",append()); // :aaaccc __block NSString *block_string = @"aaa"; NSString*(^block_append)(void)=^{ return [block_string stringByAppendingString:@"ccc"]; }; block_string = @"bbb"; NSLog(@"\
%@",block_append()); // : bbbccc

上記のコードを分析するために問題を持ってみましょう.
1.加算しない_blockは変数を変更できませんか?答えは否定的だ.まず、ここでいう修正の概念を理解します.ポインタorを修正して実際の値を修正します.blockの内部に1つのプラスしていませんblockの変数を再割り当てすると、コンパイラがエラーを報告します.コンパイラのやり方は簡単です.この変数が指す領域の内容を変更しないでください.値タイプの領域には真実の値が格納され、参照タイプの領域には別のメモリを指すポインタの値が格納されます.参照タイプの真の値の修正コンパイラには制限はありません.
まとめ:block内で値タイプ変数を変更する場合は「_block」を付け、block内で参照タイプ変数を変更する場合は状況に応じて検討し、この変数を完全に別のメモリオブジェクトに指向する必要がある場合は「_block」を加え、ポインタが指すオブジェクトを単純に変更する場合は「_block」を使用する必要はありません.
上はすべて理論の角度からObjective-Cの中のblockの使用規則を解釈して、以下はCのコードを通じてblockの実現ソースコードを見て、以下の各セグメントのコードはObjective-Cコードに対応しています:
--------Objective-C  --------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSInteger count = 10;
        NSInteger(^block)(void)=^{  return count; };
        NSLog(@"\
%ld",block()); return 0; } --------C -------- struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; NSInteger count; // __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSInteger _count, int flags=0) : count(_count) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; //block C++ static NSInteger __main_block_func_0(struct __main_block_impl_0 *__cself) { NSInteger count = __cself->count; // bound by copy return count; } //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)}; int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; NSInteger count = 10; NSInteger(*block)(void)=((NSInteger (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, count)); NSLog((NSString *)&__NSConstantStringImpl__var_folders_16_pkj1k8l97qbcp79805czcy580000gn_T_main_6bdb2c_mi_0,((NSInteger (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block)); } return 0; } static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

コード解析:Objective-Cコードのblock変数宣言と付与値を理解しやすいように行に書きました.これにより、Cコードのmain関数と比較しやすくなり、次の点が見つかりました.
  • "^"が"*"になっていて、blockが実はCの中の関数ポインタであることがわかります.
  • で最も重要なコードは、構造体__です.main_block_impl_0と関数_main_block_func_0
  • __main_block_impl_0はblockの正体です:それは1つの構造体で、count変数はblockに捕獲された外部変数の構造体の中の新しい定義です;_block_implは、最も重要な場合にFuncPtr関数ポインタを定義するブロック構造体の汎用属性をオブジェクト向けのベースクラスと見なすことができる.main_block_impl_0(...)これがblock構造体の構造関数であり、関数のパラメータには「NSInteger count」があり、これがblockインスタンスを構築する際に渡されるcount値であり、ここからblock変数が付与を初期化する際に値タイプを直接構造体に入れていることがわかるので、後続の変更はblockがキャプチャしたこのcount値に影響しない.
  • __main_block_func_0はObjective-Cで定義されたblock体で、blockが本当に実行する関数で、blockを実行すると上のblock構造体のFuncPtrポインタでこの関数を見つけて実行します.構造体のcountを直接返すので,ブロックキャプチャに影響を及ぼさない変数値の後の修正も実証した.
  • main関数のコードを見るとはっきりしていますが、最も重要なのは2番目の文です.「NSInteger(block)=((NSInteger()&_main_block_impl_0((void)_main_block_func_0,&_main_block_desc_0_DATA,count)」*まず、構造体の構造関数によって構造体を初期化し、予め定義されたblockが本当に実行する関数ポインタ、blockの記述、値変数countを入力します.次に、"&"で構造体インスタンスのアドレスを取得し、(NSInteger()でポインタの強制タイプを関数ポインタに変換して"block"ポインタ変数に付与する.最後の文のコード「((NSInteger)(_block_impl)((_block_impl)block)->FuncPtr)((_block_impl)block)」はblockを実行するためのもので、コードがはっきりしていて、関数ポインタを_block_impl**構造体ポインタは、FuncPtrが指す関数を実行します.

  • 「__block」を付けたコードを見てみましょう
    --------Objective-C  --------
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            __block NSInteger count = 10;
            NSInteger(^block)(void)=^{  return count; };
            NSLog(@"\
    %ld",block()); return 0; } --------C -------- // struct __Block_byref_count_0 { void *__isa; __Block_byref_count_0 *__forwarding; int __flags; int __size; NSInteger count; }; //block struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_count_0 *count; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_count_0 *_count, int flags=0) : count(_count->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; //block C++ static NSInteger __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_count_0 *count = __cself->count; // bound by ref return (count->__forwarding->count); } //block copy static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->count, (void*)src->count, 8/*BLOCK_FIELD_IS_BYREF*/);} //block static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->count, 8/*BLOCK_FIELD_IS_BYREF*/);} //block static struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0}; int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; __attribute__((__blocks__(byref))) __Block_byref_count_0 count = {(void*)0,(__Block_byref_count_0 *)&count, 0, sizeof(__Block_byref_count_0), 10}; NSInteger(*block)(void)=((NSInteger (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_count_0 *)&count, 570425344)); NSLog((NSString *)&__NSConstantStringImpl__var_folders_16_pkj1k8l97qbcp79805czcy580000gn_T_main_10c601_mi_0,((NSInteger (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block)); } return 0; } static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

    コード解析:
  • main関数を参照すると、以前のコードと比較して異なる箇所がはっきり見えるようになり、「NSInteger count=10」と定義されていた箇所が構造体(*_Block_byref_count_0*)ポインタの定義と初期化コードとなり、count変数を構造体の構造関数に伝達するので、「_block」の役割は、元の変数を包むための新しい構造体を定義することであることがわかる.
  • blockの構造体定義「**_main_block_impl_0*」を参照すると、count変数タイプも構造体ポインタになります.
  • block実行関数「*^_main_block_func_0*」を参照すると、count変数はすでに構造体タイプであるため、block内はポインタが指すオブジェクト値を変更することができ、blockの外でcountを変更する際に実際に変更された構造体ポインタが指す構造体オブジェクトの内部値であるという従来の理論知識を組み合わせることができる.

  • まとめ:「_block」が使用されていない場合、内部ではこの変数が直接使用され、値タイプ変数では新しい変数が直接定義され、同じ値が割り当てられ、参照タイプ変数では新しい変数が定義され、copyが定義されます.「_block」を使用すると、構造体が追加されて変数がパッケージされ、値タイプ変数はポインタになり、参照タイプはポインタになったポインタになります.
    次のコードは、グローバルstatic変数がblockで使用されるコード解析です.簡単で明確にするために、block構造体とblock実行関数のみを保持します.
    --------Objective-C  --------
    static NSString *string = @"hello";
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            NSString*(^block)(void)=^{  
              NSString *appendString = @"world";
              return [string stringByAppendingString: appendString];
        };
            NSLog(@"\
    %ld",block()); return 0; } --------C -------- static NSString *string = (NSString *)&__NSConstantStringImpl__var_folders_16_pkj1k8l97qbcp79805czcy580000gn_T_main_2919dd_mi_0; // 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 NSString * __main_block_func_0(struct __main_block_impl_0 *__cself) { NSString *appendString = (NSString *)&__NSConstantStringImpl__var_folders_16_pkj1k8l97qbcp79805czcy580000gn_T_main_2919dd_mi_1; return ((NSString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)string, sel_registerName("stringByAppendingString:"), (NSString *)appendString); }

    コード解析:
  • bloc kの構造体を見ると、copy変数をキャプチャするのではなく、構造体の実行方法でグローバル静的変数が直接使用されていることがわかるので、実行時に値を取り、常に変数の最新値を取得する.

  • 次のコードはクラスでクラス変数または属性を使用する場合のコード解析です.ここでは「TestObject」のテストクラスを定義し、クラスの「myFunction」でblock変数を定義して初期化します.
    struct __TestObject__myFunction_block_impl_0 {
      struct __block_impl impl;
      struct __TestObject__myFunction_block_desc_0* Desc;
      TestObject *self;
      __TestObject__myFunction_block_impl_0(void *fp, struct __TestObject__myFunction_block_desc_0 *desc, TestObject *_self, int flags=0) : self(_self) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __TestObject__myFunction_block_func_0(struct __TestObject__myFunction_block_impl_0 *__cself) {
      TestObject *self = __cself->self; // bound by copy
    
            (*(NSString **)((char *)self + OBJC_IVAR_$_TestObject$_test)) = (NSString *)&__NSConstantStringImpl__var_folders_16_pkj1k8l97qbcp79805czcy580000gn_T_TestObject_44e9ab_mi_0;
        }
    static void __TestObject__myFunction_block_copy_0(struct __TestObject__myFunction_block_impl_0*dst, struct __TestObject__myFunction_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __TestObject__myFunction_block_dispose_0(struct __TestObject__myFunction_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static struct __TestObject__myFunction_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __TestObject__myFunction_block_impl_0*, struct __TestObject__myFunction_block_impl_0*);
      void (*dispose)(struct __TestObject__myFunction_block_impl_0*);
    } __TestObject__myFunction_block_desc_0_DATA = { 0, sizeof(struct __TestObject__myFunction_block_impl_0), __TestObject__myFunction_block_copy_0, __TestObject__myFunction_block_dispose_0};
    
    static void _I_TestObject_myFunction(TestObject * self, SEL _cmd) {
        ((void (*)(id, SEL, Block))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__TestObject__myFunction_block_impl_0((void *)__TestObject__myFunction_block_func_0, &__TestObject__myFunction_block_desc_0_DATA, self, 570425344)));
    }
    

    コード解析:
  • 最初のコードブロックを見ると、block構造体がTestObject変数を定義し、初期化方法で変数を付与して元のselfオブジェクトに対するcopyを実現し、それを保持することで、循環参照が形成されることが明らかになった.

  • まとめ:局所値変数、局所参照タイプ変数、グローバル変数、クラス変数の理論解釈とソースコード分析を通じて、blockに対して完全に視覚障害をなくすことができると信じています.weakselfとstrongselfのソースコード分析について考えてみましょう.文章は主に私达の日常の使用の中のいくつかの概念と原理を整理して、具体的に使うかそれとも具体的なシーンを见て、みんなに役に立つことを望みます.