【iOS】ARC実装

ARC は次のツールによって実装されます。

  • Clang (LLVM コンパイラ) 3.0 以降
  • objc4 Objective-C ランタイム ライブラリ 493.9 以降

次に、clang アセンブリ出力と objc4 ライブラリのソース コードを中心とした ARC 実装を調べます。

1. __strong 修飾子

1.1 __strong 修飾子を使用した変数への代入

以下のコードを見てください

{
    
    
	id __strong obj = [[NSObject alloc] init];
}

実際、このソース コードは次の関数を呼び出すように変換できます。

// 编译器的模拟代码
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);

元のソース コードに示されているように、objc_msgSend メソッドが 2 回呼び出され、変数フィールドが終了するとオブジェクトが objc_release を通じて解放されます。

ARCが有効な場合にはreleaseメソッドは使用できませんが、コンパイラが自動的にreleaseを挿入していることがわかります。

1.2 alloc/new/copy/mutableCopy 以外のメソッドを使用する

{
    
    
	id __strong obj = [NSMutableArray array];
}

変換は次のとおりです。

// 编译器的模拟代码
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);

objc_retainAutoreleaseReturnValue() 関数は、主にプログラムの動作を最適化するために使用されます。オブジェクト自体を保持(保持)する関数に使用しますが、保持するオブジェクトはautoreleasepoolに登録されているオブジェクト、または関数の戻り値を返すメソッドである必要があります。このソースコードのように、alloc/new/copy/mutableCopy 以外のメソッドを呼び出す場合、コンパイラはこの関数を挿入します。

NSMutableArray クラスの配列メソッドのソース コードを見てみましょう。

+ (id)array {
    
    
	return [[NSMutableArray alloc] init];
}

以下はソースコードの変換です。

+ (id)array 
{
    
    
	id obj = objc_msgSend(NSMutableArray, @selector(alloc));
	objc_msgSend(obj, @selector(init));
	return objc_autoreleaseReturnValue(obj);
}

objc_autoreleaseReturnValue() 関数は、alloc/new/copy/mutableCopy メソッド以外のメソッドによって返されるオブジェクトを実装するために使用されます。このソースコードのように、autoreleasepool に登録されているオブジェクトを返します。ただし、objc_autoreleaseReturnValue 関数は objc_autorelease 関数とは異なり、通常はオブジェクトを autoreleasepool に登録することに限定されません。

上記の例では、objc_autoreleaseReturnValue() 関数は、関数を使用してメソッドまたは関数の呼び出し元の実行コマンド リストをチェックします。関数の呼び出し元が、メソッドまたは関数を呼び出した直後に objc_retainAutoreleaseReturnValue() 関数を呼び出した場合、関数は戻りません。オブジェクトは autoreleasepool に登録され、メソッドまたは関数の呼び出し元に直接渡されます。

objc_retainAutoreleaseReturnValue()関数はobjc_retain関数とは異なり、autoreleasepoolにオブジェクトを登録せずに返却した場合でも正しくオブジェクトを取得できます。

上記2つの機能の連携により、オブジェクトをautoreleasepoolに登録せずに直接転送できるようになり、この処理が最適化されました。

ここに画像の説明を挿入します

2. __weak 修飾子

2.1 __weak 修飾子を使用して変数に値を代入します。

{
    
    
	id _weak obj1 = obj;
}

変数 obj に __strong 修飾子が追加され、オブジェクトが割り当てられているとします。

// 编译器的模拟代码
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);

objc_initWeak 関数は、__weak 修飾子を使用して変数を 0 に初期化した後、割り当てられたオブジェクトをパラメータとして objc_storeWeak 関数を呼び出します。

objc_destroyWeak 関数は、パラメータとして 0 を指定して objc_storeWeak 関数を呼び出します。

つまり、前のソース コードは次のソース コードと同じです。

// 编译器模拟代码
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);

objc_storeWeak関数は、第2引数の代入オブジェクトをキー値として、第1引数(__weak修飾子が付いた変数)のアドレスをweakテーブルに登録します。2 番目のパラメータが 0 の場合、変数のアドレスはウィーク テーブルから削除されます。

弱いテーブルは参照カウント テーブルと同じであり、ハッシュ テーブルとして実装されます。破棄されたオブジェクトのアドレスをキー値として取得することで、対応する __weak 修飾子変数のアドレスを迅速に取得できます。キー値には複数の変数のアドレスを登録できます。

2.2 誰も保持していないオブジェクトを破棄するときのプログラムの動作;

(1) objc_release
(2) 参照カウントが0なのでdealloc実行
(3) _objc_rootDealloc
(4) object_dispose
(5) objc_destructInstanse
(6) objc_clear_deallocating

objc_clear_deallocating 関数のアクションは次のとおりです。

  1. キー値として弱いテーブルから破棄されたオブジェクトのアドレスを取得します。
  2. レコードに含まれる __weak 修飾子を持つ変数のすべてのアドレスに nil を代入します。
  3. 弱いテーブルからレコードを削除します。
  4. アドレスが放棄されたオブジェクトのキー値であるレコードを参照カウントテーブルから削除します。

以上の手順により、__weak修飾子を付けた変数が参照するオブジェクトを破棄する際に、変数にnilを代入する機能が実現します。

__weak 修飾子が付いた変数を多数使用すると、対応する CPU リソースが消費されることがわかります。したがって、循環参照を避ける必要がある場合にのみ __weak 修飾子を使用します。

2.3 自分で生成・保持したオブジェクトを __weak 修飾子を付けて変数に代入します。

{
    
    
	id __weak obj = [[NSObject alloc] init];
}

__weak 修飾子を持つ変数はオブジェクトを保持できないため、オブジェクトは解放されて破棄され、コンパイラ警告が発生します。
ここに画像の説明を挿入します

コンパイラがこのソース コードをどのように処理するかを見てみましょう。

// 编译器的模拟代码
id obj;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_masSend(tmp, @selector(init));
objc_initWeak(&obj, tmp);
objc_release(tmp);
objc_destroyWeak(&object);

自身で生成・保持していたオブジェクトは、objc_initWeak関数によって__weak修飾子付きの変数に代入されますが、コンパイラは保持者がないと判断するため、objc_release関数によって即座にオブジェクトを解放・破棄します。

2.4 その他

『Objective-C Advanced Programming』の本で説明されているように、ARC では、__weak 修飾子を付けた変数を使用することは、autoreleasepool に登録されているオブジェクトを使用することになります。実際、私が持っているバージョンが古すぎて、バージョン変更が多すぎることが原因である可能性があります。少なくとも Xcode13.3.1 では、このステートメントは適用されなくなっていることを確認しました。

以下のソースコードを見てください。

    @autoreleasepool {
    
    
        id obj = [[NSObject alloc] init];
        
        {
    
    
            id __weak o = obj;
            NSLog(@"%@", o);
            NSLog(@"%@", o);
            NSLog(@"%@", o);
            _objc_autoreleasePoolPrint();
        }
    }

ソースコードは次のように実行されます。ここに画像の説明を挿入します

__weak 修飾子が付いた変数が複数回使用されているにも関わらず、オブジェクトが autoreleasepool に登録されていないことがわかります。

わずかに変更:

    @autoreleasepool {
    
    
        id obj = [[NSObject alloc] init];
        
        {
    
    
            id __weak o = obj;
            id __autoreleasing tmp = o;
            NSLog(@"%@", o);
            NSLog(@"%@", o);
            NSLog(@"%@", o);
            _objc_autoreleasePoolPrint();
        }
    }

実行結果は次のとおりです。
ここに画像の説明を挿入します

ご覧のとおり、オブジェクトはid __autoreleasing tmp = o;実行時にのみ autoreleasepool に登録されます。

3. __autorelease 修飾子

__autoreleasing 修飾子を使用してオブジェクトを変数に代入することは、ARC が無効な場合にオブジェクトの autorelease メソッドを呼び出すことと同じです。

3.1 __autoreleasing 修飾子を使用して変数に値を代入します。

    @autoreleasepool {
    
    
        id __autoreleasing obj = [[NSObject alloc] init];
    }

次のような変換を行うことができます。

// 编译器模拟代码
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

ARC は、有効時と無効時でソース コード上で異なる動作をしますが、自動解放の機能はまったく同じです。

3.2 alloc/new/copy/mutableCopy 以外のメソッドを使用する

    @autoreleasepool {
    
    
        id __autoreleasing obj = [NSMutableArray array];
    }

次のような変換を行うことができます。

// 编译器的模拟代码
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutireleasedReturnValue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

オブジェクトの保持方法はallocメソッドからobjc_retainAutireleaseReturnValue関数に変更されましたが、autoreleasepoolの登録方法はobjc_autorelease関数のままです。

4. ARC の仕組み

ARC の動作原理はおおよそ次のとおりです。ソース コードをコンパイルすると、コンパイラーはソース コード内の各オブジェクトのライフ サイクルを分析し、対応する参照カウント オペレーション コードを追加します。オブジェクトのライフ サイクルに基づいて、適切な場所に保持および解放されます。これらのオブジェクトと自動解放。

ARC はコンパイル時に機能する技術ソリューションであり、次のような利点があります。

  1. コンパイル後は、ARC コードと非 ARC コードの間に違いがないため、この 2 つはソース コード内で共存できます。実際、パラメーター -fno-objc-arc をコンパイルすることで、一部のソース コードの ARC 機能をオフにすることができます。
  2. ガベージ コレクションなどのメモリ管理ソリューションと比較して、ARC は追加の実行時オーバーヘッドをもたらさないため、アプリケーションの実行効率に影響を与えません。それどころか、ARC は各オブジェクトのライフサイクルを詳細に分析できるため、参照カウントを手動で管理するよりも効率的になる可能性があります。たとえば、関数の先頭でオブジェクトの参照カウント +1 の操作があり、次に -1 の操作がある場合、コンパイラは両方の操作を最適化できます。

ARC はコンパイル時と実行時に何を行いますか?

  1. ARC は、コンパイル中に、互いに打ち消し合う保持、解放、および自動解放の操作を簡素化します。
  2. ARC には、自動解放の冗長な操作を検出して実行時に保持できるランタイム コンポーネントが含まれています。コードを最適化するために、自動的に解放されたオブジェクトをメソッドで返すときに特別な関数が実行されます。

おすすめ

転載: blog.csdn.net/m0_63852285/article/details/131783635