静的ライブラリのObjective-Cカテゴリ


153

静的ライブラリをiPhoneプロジェクトに適切にリンクする方法を教えてください。アプリプロジェクトに追加された静的ライブラリプロジェクトを直接依存関係(ターゲット->一般->直接依存関係)として使用し、すべて正常に機能しますが、カテゴリは機能します。静的ライブラリで定義されたカテゴリがアプリで機能していません。

だから私の質問は、いくつかのカテゴリを持つ静的ライブラリを他のプロジェクトに追加する方法ですか?

そして一般的に、他のプロジェクトのアプリプロジェクトコードで使用するベストプラクティスは何ですか?


1
よく、いくつかの答えを発見し、この質問が既に(申し訳ありません、それは見逃しここで答えたようだstackoverflow.com/questions/932856/...
ウラジミール

回答:


228

解決策: Xcode 4.2以降は、ライブラリ自体ではなくライブラリにリンクしているアプリケーションに移動し、プロジェクトナビゲーターでプロジェクトをクリックし、アプリのターゲットをクリックして、設定をビルドし、「その他」を検索するだけです。リンカーフラグ」をクリックし、+ボタンをクリックして、「-ObjC」を追加します。'-all_load'および '-force_load'は不要になりました。

詳細: さまざまなフォーラム、ブログ、アップルドキュメントでいくつかの回答を見つけました。次に、検索と実験の短い要約を作成します。

問題の原因は次のとおりです(アップルテクニカルQ&A QA1490 https://developer.apple.com/library/content/qa/qa1490/_index.htmlからの引用):

Objective-Cは、各関数(またはObjective-Cではメソッド)のリンカーシンボルを定義していません。代わりに、リンカーシンボルはクラスごとにのみ生成されます。既存のクラスをカテゴリで拡張すると、リンカはコアクラス実装のオブジェクトコードとカテゴリ実装を関連付けることを認識しません。これにより、結果のアプリケーションで作成されたオブジェクトが、カテゴリーで定義されたセレクターに応答しなくなります。

そしてそれらの解決策:

この問題を解決するには、静的ライブラリが-ObjCオプションをリンカーに渡す必要があります。このフラグにより​​、リンカーはObjective-Cクラスまたはカテゴリーを定義するライブラリー内のすべてのオブジェクトファイルをロードします。このオプションは通常、(アプリケーションに追加のオブジェクトコードが読み込まれるため)実行可能ファイルが大きくなりますが、既存のクラスのカテゴリを含む効果的なObjective-C静的ライブラリを正常に作成できます。

また、iPhone開発に関するFAQにも推奨事項があります。

静的ライブラリ内のすべてのObjective-Cクラスをリンクするにはどうすればよいですか?その他のリンカーフラグのビルド設定を-ObjCに設定します。

とフラグの説明:

- all_loadのロードに静的アーカイブライブラリのすべてのメンバー。

- にObjCは、 Objective-Cのクラスまたはカテゴリを実装する静的アーカイブライブラリのすべてのメンバーをロードします。

- force_load(path_to_archive)は、指定された静的なアーカイブライブラリのすべてのメンバーをロードします。注:-all_loadは、すべてのアーカイブのすべてのメンバーを強制的にロードします。このオプションを使用すると、特定のアーカイブをターゲットにできます。

* force_loadを使用して、アプリのバイナリサイズを削減し、all_loadが原因で発生する可能性がある競合を回避できます。

はい、プロジェクトに追加された* .aファイルで動作します。しかし、直接依存として追加されたlibプロジェクトで問題が発生しました。しかし、後でそれが私のせいだとわかりました-直接依存関係プロジェクトが適切に追加されなかった可能性があります。私がそれを削除し、手順で再度追加すると:

  1. アプリプロジェクトにlibプロジェクトファイルをドラッグ&ドロップします(または、[プロジェクト]-> [プロジェクトに追加]で追加します)。
  2. libプロジェクトアイコンの矢印をクリックします-mylib.aファイル名が表示されます。このmylib.aファイルをドラッグし、[ターゲット]-> [ライブラリとバイナリをリンク]にドロップします。
  3. 最初のページ(一般)でターゲット情報を開き、依存関係リストにライブラリを追加します

その後はすべて正常に動作します。私の場合は「-ObjC」フラグで十分でした。

また、http://iphonedevelopmentexperiences.blogspot.com/2010/03/categories-in-static-library.htmlブログのアイデアにも興味を持っていました。著者は、-all_loadまたは-ObjCフラグを設定しなくても、libのカテゴリを使用できると述べています。彼は、カテゴリh / mファイルに空のダミークラスインターフェイス/実装を追加して、リンカにこのファイルを強制的に使用させます。そして、はい、このトリックは仕事をします。

しかし、作者はまた彼はダミーオブジェクトをインスタンス化しなかったとも述べました。うーん…私が見つけたように、我々はカテゴリファイルからいくつかの「実際の」コードを明示的に呼び出すべきです。したがって、少なくともクラス関数を呼び出す必要があります。そして、ダミークラスも必要ありません。単一のc関数も同じことを行います。

したがって、libファイルを次のように記述すると、

// mylib.h
void useMyLib();

@interface NSObject (Logger)
-(void)logSelf;
@end


// mylib.m
void useMyLib(){
    NSLog(@"do nothing, just for make mylib linked");
}


@implementation NSObject (Logger)
-(void)logSelf{
    NSLog(@"self is:%@", [self description]);
}
@end

そして、useMyLib()を呼び出すと、アプリプロジェクトの任意の場所で、次にクラスでlogSelfカテゴリメソッドを使用できます。

[self logSelf];

そしてテーマに関する他のブログ:

http://t-machine.org/index.php/2009/10/13/how-to-make-an-iphone-static-library-part-1/

http://blog.costan.us/2009/12/fat-iphone-static-libraries-device-and.html


8
アップルのテクニカルノートは、「この問題を解決するには、スタティックライブラリに対してリンクしているターゲットが-ObjCオプションをリンカーに渡す必要がある」と変更されているようです。これは、上で引用したものの逆です。ライブラリ自体ではなく、アプリをリンクするときに含める必要があることを確認しました。
Ken Aspeslagh、

doc developer.apple.com/library/mac/#qa/qa1490/_index.htmlによると、-all_loadまたは-force_loadフラグを使用する必要があります。前述のように、リンカーには64ビットMacアプリとiPhoneアプリにバグがあります。「重要:64ビットおよびiPhone OSアプリケーションの場合、-ObjCがカテゴリのみを含みクラスを含まないスタティックライブラリからオブジェクトファイルをロードできないようにするリンカーバグがあります。回避策は-all_loadまたは-force_loadフラグを使用することです。」
ロビン

2
@Ken Aspelagh:ありがとう、同じ問題がありました。-ObjCフラグと-all_loadフラグは、ライブラリではなくアプリ自体に追加する必要があります。
チタニウム

3
すばらしい回答です。ただし、この質問の初心者は、現在は時代遅れであることに注意してください。tonklonの答えをチェックしてくださいstackoverflow.com/a/9224606/322748(all_load / force_load不要になった)
ジェイ・バイエル板

私はこれらの事柄に30分近く行き詰まり、試行錯誤をしてそれを作り出した。まあありがとう。この答えは+1の価値があり、あなたはそれを得ました!!!
Deepukjayan

118

ウラジミールからの回答は実際にはかなり良いですが、私はここでいくつかの背景知識を提供したいと思います。多分いつの日か誰かが私の返事を見つけて、それが役に立つと思うかもしれません。

コンパイラは、ソースファイル(.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. 1つの解決策は-all_load、リンカー呼び出しに追加することです。そのリンカフラグは実際には何をしますか?実際には、リンカに次のように伝えます。「使用中のシンボルが表示されているかどうかに関係なく、すべてのアーカイブのすべてのオブジェクトファイルをロードします

  2. 別の解決策は-force_load、アーカイブへのパスを含むリンカー呼び出しに追加することです。このフラグはと同じよう-all_loadに機能しますが、指定されたアーカイブに対してのみ機能します。もちろん、これも機能します。

  3. 最も一般的な解決策は-ObjC、リンカー呼び出しに追加することです。そのリンカフラグは実際には何をしますか?このフラグはリンカーに「Obj-Cコードが含まれている場合は、すべてのアーカイブからすべてのオブジェクトファイルをロードする」ことを伝えます。また、「任意のObj-Cコード」にはカテゴリが含まれます。これは同様に機能し、Obj-Cコードを含まないオブジェクトファイルのロードを強制しません(これらはまだオンデマンドでのみロードされます)。

  4. 別の解決策は、かなり新しいXcodeビルド設定Perform Single-Object Prelinkです。この設定は何をしますか?有効にすると、すべてのオブジェクトファイル(ソースファイルごとに1つあることを忘れないでください)が1つのオブジェクトファイル(実際のリンクではないため、PreLinkという名前)とこの1つのオブジェクトファイル(「マスターオブジェクト」とも呼ばれる)にマージされます。ファイル」)がアーカイブに追加されます。マスターオブジェクトファイルのシンボルが使用中と見なされると、マスターオブジェクトファイル全体が使用中と見なされ、そのオブジェクトのすべてのObjective-C部分が常に読み込まれます。また、クラスは通常のシンボルなので、すべてのカテゴリを取得するには、このような静的ライブラリの単一のクラスを使用するだけで十分です。

  5. 最終的な解決策は、ウラジミールが答えの最後に付け加えたトリックです。カテゴリのみを宣言するソースファイルに「偽のシンボル」を配置します。実行時にカテゴリのいずれかを使用する場合は、コンパイル時に偽のシンボルを何らかの方法で参照してください。これにより、オブジェクトファイルがリンカーによってロードされ、その結果、その中のすべての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すると、リンカは、使用中のシンボルのために、どのライブラリからどのオブジェクトファイルをロードしたかをビルドログに出力します。使用中と考えられる最初のシンボルのみを出力しますが、そのオブジェクトファイルを使用している唯一のシンボルであるとは限りません。


1
-whyloadリンカが何かをしている理由をデバッグしようとすることは非常に難しいことに言及していただきありがとうございます!
ベンS

オプションありDead Code StrippingではBuild Settings>Linking。で-dead_strip追加したものと同じOther Linker Flagsですか?
Xiao

1
@Seanはい、同じです。ただ、すべてのビルド設定のために存在する「クイックヘルプ」を読み、答えはすぐそこです:postimg.org/image/n7megftnr/full
Mecki

@Meckiありがとう。私は取り除こうとした-ObjCので、あなたのハックを試みましたが、それは不平を言い"import_NSString_jsonObject()", referenced from: importCategories() in main.o ld: symbol(s) not foundます。import_NSString_jsonObjectという埋め込みフレームワークをに入れ、の最後にwith ステートメントをUtility追加#import <Utility/Utility.h>します。__attribute__AppDelegate.h
Xiao

@Seanリンカーがシンボルを見つけられない場合、そのシンボルを含む静的ライブラリに対してリンクしていません。フレームワークからファイルをインポートするだけでは、フレームワークに対してXcodeリンクは作成されません。フレームワークは、フレームワークビルドフェーズとのリンクで明示的にリンクする必要があります。リンクの問題について独自の質問を開くこともできます。コメントで回答するのは面倒で、ビルドログ出力などの情報を提供することもできません。
メッキ

24

この問題はLLVMで修正れました。フィックスはLLVM 2.9の一部として出荷されます。フィックスを含む最初のXcodeバージョンは、LLVM 3.0に付属するXcode 4.2です。XCode 4.2での作業が引き続き必要な場合-all_loadまたはの使用は-force_load -ObjC必要なくなりました。


あなたはこれについて確信を持っていますか?Xcode 4.3.2を使用してiOSプロジェクトで作業していて、LLVM 3.1でコンパイルしていますが、これはまだ問題でした。
アシュレイミルズ

OK、少し不正確でした。-ObjCフラグはまだ必要とされ、常になります。回避策は、-all_loadまたはの使用でした-force_load。そして、それはもう必要ありません。上記の回答を修正しました。
tonklon

-all_loadフラグを含めることに不必要な点はありますか?コンパイル/起動時間に何らかの影響がありますか?
ZS

Xcodeバージョン4.5(4G182)を使用していて、-ObjCフラグを使用すると、認識できないセレクターエラーが、使用しようとしているサードパーティの依存関係からObjective Cランタイムの深さのようなものに移動します: "-[__ NSArrayMマップ:]:認識されないセレクターがインスタンスに送信されました... "。手がかりはありますか?
Robert Atkins

16

静的ライブラリをコンパイルするときにこの問題を完全に解決するには、次のことを行う必要があります。

Xcode Build Settingsに移動して、Perform Single-Object PrelinkをYESにGENERATE_MASTER_OBJECT_FILE = YES設定するか 、ビルド構成ファイルで設定します。

デフォルトでは、リンカーは.mファイルごとに.oファイルを生成します。したがって、カテゴリは異なる.oファイルを取得します。リンカーが静的ライブラリの.oファイルを参照するとき、クラスごとにすべてのシンボルのインデックスを作成しません(ランタイムは何でもかまいません)。

このディレクティブは、すべてのオブジェクトを1つの大きな.oファイルにまとめるようリンカーに要求します。これにより、静的ライブラリを処理するリンカーに、すべてのクラスカテゴリのインデックスを取得するように強制します。

それが明確になることを願っています。


これにより、リンクターゲットに-ObjCを追加しなくても、問題が修正されました。
マシュークレンショー2013

BlocksKitライブラリの最新バージョンに更新した後、この設定を使用して問題を修正する必要がありました(すでに-ObjCフラグを使用していましたが、まだ問題が発生しています)。
rakmoh 2013年

1
実際、あなたの答えは正しくありません。「同じクラスのすべてのカテゴリを1つの.oファイルにまとめるようリンカーに要求する」のではなく、静的ライブラリを作成する前に、すべてのオブジェクトファイル(.o)を単一の大きなオブジェクトファイルにリンクするようリンカーに要求しますそれら/それ。ライブラリからシンボルが参照されると、すべてのシンボルが読み込まれます。ただし、シンボルが参照されていない場合は機能しません(たとえば、ライブラリにカテゴリしかない場合は機能しません)。
Mecki

NSDataなどの既存のクラスにカテゴリを追加した場合、これが機能するとは思わない。
ボブホワイトマン、2014年

私も既存のクラスにカテゴリを追加できません。実行時にプラグインがそれらを認識できません。
David Dunham

9

静的ライブラリのリンクに関する議論が行われるたびにほとんど言及されない1つの要因は、ビルドフェーズ->コピーファイルにカテゴリ自体も含め、静的ライブラリ自体のソースをコンパイルする必要があるという事実です。

Appleは最近発表されたiOSでのスタティックライブラリの使用でもこの事実を強調していません。

私は丸一日を費やして-objCや-all_loadなどのあらゆる種類のバリエーションを試しましたが、そこから何も得られませんでした。この質問が私の問題に気付きました。(私を誤解しないでください.. -objCを実行する必要があります..しかし、それだけではありません)。

また、常に私を助けてきた別のアクションは、常に最初にインクルードされた静的ライブラリを常に自分でビルドすることです。次に、囲んでいるアプリケーションをビルドします。


-1

おそらく、静的ライブラリの「パブリック」ヘッダーであるカテゴリが必要です:#import "MyStaticLib.h"

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.