ウラジミールからの回答は実際にはかなり良いですが、私はここでいくつかの背景知識を提供したいと思います。多分いつの日か誰かが私の返事を見つけて、それが役に立つと思うかもしれません。
コンパイラは、ソースファイル(.c、.cc、.cpp、.m)をオブジェクトファイル(.o)に変換します。ソースファイルごとに1つのオブジェクトファイルがあります。オブジェクトファイルには、シンボル、コード、データが含まれています。オブジェクトファイルは、オペレーティングシステムでは直接使用できません。
動的ライブラリ(.dylib)、フレームワーク、ロード可能なバンドル(.bundle)、または実行可能バイナリを構築すると、これらのオブジェクトファイルがリンカによってリンクされ、オペレーティングシステムが「使用可能」と見なすもの、たとえば特定のメモリアドレスに直接ロードします。
ただし、静的ライブラリを構築する場合、これらすべてのオブジェクトファイルは単に大きなアーカイブファイルに追加されるため、静的ライブラリの拡張子(アーカイブの場合は.a)になります。したがって、.aファイルはオブジェクト(.o)ファイルのアーカイブにすぎません。圧縮なしのTARアーカイブまたはZIPアーカイブを考えてみてください。.oファイルの束全体よりも1つの.aファイルをコピーする方が簡単です(.classファイルを.jarアーカイブにパックして配布を容易にするJavaと同様)。
バイナリを静的ライブラリ(=アーカイブ)にリンクすると、リンカーはアーカイブ内のすべてのシンボルのテーブルを取得し、これらのシンボルのどれがバイナリによって参照されているかを確認します。参照されるシンボルを含むオブジェクトファイルのみが実際にリンカーによって読み込まれ、リンクプロセスによって考慮されます。たとえば、アーカイブに50個のオブジェクトファイルがあり、バイナリで使用されるシンボルが20個しか含まれていない場合、それらの20個だけがリンカーによって読み込まれ、残りの30個はリンクプロセスで完全に無視されます。
これらの言語はコンパイル時にできる限り多くのことをしようとするため、これはCおよびC ++コードに対して非常にうまく機能します(ただし、C ++にはいくつかのランタイムのみの機能もあります)。ただし、Obj-Cは別の種類の言語です。Obj-Cはランタイム機能に大きく依存しており、多くのObj-C機能は実際にはランタイムのみの機能です。Obj-Cクラスには、実際にはC関数またはグローバルC変数に相当するシンボルがあります(少なくとも現在のObj-Cランタイムでは)。リンカーは、クラスが参照されているかどうかを確認できるため、使用中のクラスを判別できます。静的ライブラリのオブジェクトファイルからクラスを使用する場合、リンカは使用中のシンボルを検出するため、このオブジェクトファイルはリンカによってロードされます。カテゴリは実行時のみの機能であり、カテゴリはクラスや関数のようなシンボルではありません。つまり、リンカはカテゴリが使用中かどうかを判断できません。
リンカがObj-Cコードを含むオブジェクトファイルをロードする場合、そのすべてのObj-C部分は常にリンクステージの一部です。したがって、そこからのシンボルが「使用中」(クラス、関数、グローバル変数など)と見なされてカテゴリを含むオブジェクトファイルが読み込まれると、カテゴリも読み込まれ、実行時に使用できるようになります。 。ただし、オブジェクトファイル自体が読み込まれていない場合、その中のカテゴリは実行時に使用できません。含むオブジェクトファイルのみカテゴリがされないことが含まれていないため、ロードされていない何のシンボルリンカが考え、これまで「使用中」を検討します。そして、これがここでの全体の問題です。
いくつかの解決策が提案されてきましたが、これがすべて一緒にどのように機能するかがわかったところで、提案された解決策をもう一度見てみましょう。
1つの解決策は-all_load
、リンカー呼び出しに追加することです。そのリンカフラグは実際には何をしますか?実際には、リンカに次のように伝えます。「使用中のシンボルが表示されているかどうかに関係なく、すべてのアーカイブのすべてのオブジェクトファイルをロードします。
別の解決策は-force_load
、アーカイブへのパスを含むリンカー呼び出しに追加することです。このフラグはと同じよう-all_load
に機能しますが、指定されたアーカイブに対してのみ機能します。もちろん、これも機能します。
最も一般的な解決策は-ObjC
、リンカー呼び出しに追加することです。そのリンカフラグは実際には何をしますか?このフラグはリンカーに「Obj-Cコードが含まれている場合は、すべてのアーカイブからすべてのオブジェクトファイルをロードする」ことを伝えます。また、「任意のObj-Cコード」にはカテゴリが含まれます。これは同様に機能し、Obj-Cコードを含まないオブジェクトファイルのロードを強制しません(これらはまだオンデマンドでのみロードされます)。
別の解決策は、かなり新しいXcodeビルド設定Perform Single-Object Prelink
です。この設定は何をしますか?有効にすると、すべてのオブジェクトファイル(ソースファイルごとに1つあることを忘れないでください)が1つのオブジェクトファイル(実際のリンクではないため、PreLinkという名前)とこの1つのオブジェクトファイル(「マスターオブジェクト」とも呼ばれる)にマージされます。ファイル」)がアーカイブに追加されます。マスターオブジェクトファイルのシンボルが使用中と見なされると、マスターオブジェクトファイル全体が使用中と見なされ、そのオブジェクトのすべてのObjective-C部分が常に読み込まれます。また、クラスは通常のシンボルなので、すべてのカテゴリを取得するには、このような静的ライブラリの単一のクラスを使用するだけで十分です。
最終的な解決策は、ウラジミールが答えの最後に付け加えたトリックです。カテゴリのみを宣言するソースファイルに「偽のシンボル」を配置します。実行時にカテゴリのいずれかを使用する場合は、コンパイル時に偽のシンボルを何らかの方法で参照してください。これにより、オブジェクトファイルがリンカーによってロードされ、その結果、その中のすべてのObj-Cコードもロードされます。たとえば、関数本体が空の関数(呼び出されても何もしない)、またはアクセスされたグローバル変数(グローバルint
一度読み取るか、一度書き込むと、これで十分です。上記の他のすべてのソリューションとは異なり、このソリューションは、実行時に使用可能なカテゴリに関する制御をコンパイル済みコードにシフトします(リンクして使用できるようにする場合は、シンボルにアクセスします。そうでない場合は、シンボルにアクセスせず、リンカーは無視します。それ)。
それはすべての人々です。
ああ、待って、もう1つ
あります-dead_strip
。リンカにはという名前のオプションがあります。このオプションは何をしますか?リンカがオブジェクトファイルをロードすることを決定した場合、オブジェクトファイルのすべてのシンボルは、使用されているかどうかに関係なく、リンクされたバイナリの一部になります。たとえば、オブジェクトファイルには100個の関数が含まれていますが、バイナリでは1つしか使用されません。オブジェクトファイルは全体として追加されるか、まったく追加されないため、100個の関数はすべてバイナリに追加されます。オブジェクトファイルの部分的な追加は、通常、リンカーではサポートされていません。
ただし、リンカに「デッドストリップ」を指示すると、リンカは最初にすべてのオブジェクトファイルをバイナリに追加し、すべての参照を解決し、最後に使用されていないシンボル(または、使用する)。使用されていないことが判明したすべてのシンボルは、最適化ステージの一部として削除されます。上記の例では、99個の未使用の関数が再び削除されています。これは-load_all
、のようなオプションを使用する場合、-force_load
またはPerform Single-Object Prelink
これらのオプションがバイナリサイズを劇的に拡大し、デッドストリッピングによって未使用のコードとデータが再び削除される場合に非常に役立ちます。
デッドストリッピングはCコードで非常にうまく機能し(たとえば、未使用の関数、変数、定数は期待どおりに削除されます)、C ++でも非常に機能します(たとえば、未使用のクラスが削除されます)。完全ではありません。削除しても大丈夫な場合でも、一部のシンボルが削除されない場合がありますが、ほとんどの場合、これらの言語では非常にうまく機能します。
Obj-Cはどうですか?気にしないで!Obj-Cのデッドストリッピングはありません。Obj-Cはランタイム機能言語であるため、コンパイラーはコンパイル時にシンボルが実際に使用されているかどうかを判断できません。たとえば、直接参照するコードがない場合、Obj-Cクラスは使用されていません。違う!クラス名を含む文字列を動的に作成し、その名前のクラスポインターを要求して、クラスを動的に割り当てることができます。例えばの代わりに
MyCoolClass * mcc = [[MyCoolClass alloc] init];
私も書くことができました
NSString * cname = @"CoolClass";
NSString * cnameFull = [NSString stringWithFormat:@"My%@", cname];
Class mmcClass = NSClassFromString(cnameFull);
id mmc = [[mmcClass alloc] init];
どちらの場合もmmc
、クラス「MyCoolClass」のオブジェクトへの参照ですが、2番目のコードサンプルではこのクラスへの直接参照はありません(静的文字列としてのクラス名でさえも)。すべてが実行時にのみ発生します。そして、それはクラスが実際に実際のシンボルであるにもかかわらずです。カテゴリは実際のシンボルではないため、カテゴリの場合はさらに悪くなります。
したがって、数百のオブジェクトを含む静的ライブラリがあり、ほとんどのバイナリがそれらのうちのいくつかしか必要としない場合は、上記のソリューション(1)から(4)を使用しないことをお勧めします。そうしないと、ほとんどのクラスが使用されない場合でも、これらすべてのクラスを含む非常に大きなバイナリが作成されます。クラスの場合、クラスには実際のシンボルがあるため、通常は特別なソリューションはまったく必要ありません。また、直接参照する限り(2番目のコードサンプルとは異なります)、リンカはその使用法を独自に識別します。ただし、カテゴリについては、ソリューション(5)を検討してください。これにより、本当に必要なカテゴリのみを含めることができます。
たとえば、NSDataのカテゴリが必要な場合、たとえば圧縮/解凍メソッドをそれに追加する場合は、ヘッダーファイルを作成します。
// NSData+Compress.h
@interface NSData (Compression)
- (NSData *)compressedData;
- (NSData *)decompressedData;
@end
void import_NSData_Compression ( );
と実装ファイル
// NSData+Compress
@implementation NSData (Compression)
- (NSData *)compressedData
{
// ... magic ...
}
- (NSData *)decompressedData
{
// ... magic ...
}
@end
void import_NSData_Compression ( ) { }
コード内のどこかimport_NSData_Compression()
が呼び出されることを確認してください。どこで呼び出されても、どのくらいの頻度で呼び出されてもかまいません。実際には、実際に呼び出す必要はまったくありません。リンカがそう思っていれば十分です。たとえば、次のコードをプロジェクトの任意の場所に配置できます。
__attribute__((used)) static void importCategories ()
{
import_NSData_Compression();
// add more import calls here
}
importCategories()
コード内で呼び出す必要はありません。属性により、コンパイラーとリンカーは、呼び出されていない場合でも、呼び出されたと認識します。
そして最後のヒント:最後のリンク呼び出しに
追加-whyload
すると、リンカは、使用中のシンボルのために、どのライブラリからどのオブジェクトファイルをロードしたかをビルドログに出力します。使用中と考えられる最初のシンボルのみを出力しますが、そのオブジェクトファイルを使用している唯一のシンボルであるとは限りません。