web-dev-qa-db-ja.com

Objective-C名前空間の衝突を解決する最良の方法は何ですか?

Objective-Cには名前空間がありません。 Cによく似ており、すべてが1つのグローバル名前空間内にあります。一般的な方法は、クラスの頭にイニシャルを付けることです。 IBMで作業している場合は、「IBM」というプレフィックスを付けることができます。 Microsoftで働いている場合は、「MS」を使用できます。等々。イニシャルはプロジェクトを指す場合があります。 Adiumはクラスの前に「AI」を付けます(イニシャルを取得できる会社はないため)。 Appleは、クラスにNSをプレフィックスし、このプレフィックスはAppleのみに予約されています。

これまでのところ。ただし、前のクラス名に2〜4文字を追加することは、非常に限られた名前空間です。例えば。 MSまたはAIの意味はまったく異なる場合があり(AIは人工知能など)、他の開発者がそれらを使用して同じ名前のクラスを作成する場合があります。 Bang、名前空間の衝突。

さて、これが自分のクラスの1つと使用している外部フレームワークの1つとの衝突である場合、クラスの命名を簡単に変更できます。 しかし、2つの外部フレームワークを使用するとどうなりますか?両方のフレームワークにはソースがなく、変更できませんか?アプリケーションはそれらの両方とリンクし、名前の競合。これらをどのように解決しますか?両方のクラスを引き続き使用できるようにそれらを回避する最良の方法は何ですか?

Cでは、ライブラリに直接リンクしないでこれらを回避できます。代わりに、dlopen()を使用して実行時にライブラリをロードし、dlsym()を使用して探しているシンボルを見つけ、それをグローバルシンボル(任意の名前を付けることができます)、このグローバルシンボルを使用してアクセスします。例えば。一部のCライブラリにopen()という名前の関数があるために競合がある場合、myOpenという名前の変数を定義し、ライブラリのopen()関数を指すようにすることができます。したがって、システムのopen()を使用する場合は、 open()を使用するだけで、もう一方を使用する場合は、myOpen識別子を介してアクセスします。

Objective-Cでも同様のことが可能ですか?そうでない場合は、名前空間の競合を解決するために使用できる他の巧妙でトリッキーなソリューションはありますか?何か案は?


更新:

これを明確にするために、名前空間の衝突を事前に回避する方法や、より良い名前空間を作成する方法を提案する回答を歓迎します。ただし、それらは答えとして受け入れません。なぜなら、彼らは私の問題を解決しないからです。 2つのライブラリがあり、クラス名が衝突しています。それらを変更することはできません。どちらのソースも持っていません。衝突はすでにそこにあり、事前に衝突を回避する方法についてのヒントは役に立たなくなります。これらをこれらのフレームワークの開発者に転送し、将来より良い名前空間を選択することを期待できますが、当面は単一のアプリケーション内でフレームワークを使用するためのソリューションを探しています。これを可能にするソリューションはありますか?

173
Mecki

両方のフレームワークのクラスを同時に使用する必要がなく、NSBundleのアンロードをサポートするプラットフォームをターゲットにしている場合(OS X 10.4以降、GNUStepのサポートなし)、パフォーマンスは本当にあなたにとって問題ではありませんクラスを使用する必要があるたびに1つのフレームワークをロードし、次に他のフレームワークを使用する必要があるときにアンロードしてもう1つのフレームワークをロードできること。

私の最初のアイデアは、NSBundleを使用してフレームワークの1つをロードし、そのフレームワーク内のクラスをコピーまたは名前変更してから、他のフレームワークをロードすることでした。これには2つの問題があります。まず、クラスの名前を変更またはコピーするためにポイントされたデータをコピーする関数が見つかりませんでした。名前が変更されたクラスを参照する最初のフレームワークの他のクラスは、他のフレームワークからクラスを参照します。

IMPが指すデータをコピーする方法があれば、クラスをコピーしたり名前を変更したりする必要はありません。新しいクラスを作成してから、ivar、メソッド、プロパティ、およびカテゴリをコピーできます。さらに多くの作業がありますが、可能です。ただし、フレームワーク内の他のクラスが間違ったクラスを参照しているという問題は依然としてあります。

編集:CランタイムとObjective-Cランタイムの基本的な違いは、私が理解しているように、ライブラリがロードされると、それらのライブラリの関数には参照するシンボルへのポインタが含まれますが、Objective-Cでは、 thsoeシンボルの名前。したがって、この例では、dlsymを使用して、メモリ内のシンボルのアドレスを取得し、別のシンボルにアタッチできます。ライブラリ内の他のコードは、元のシンボルのアドレスを変更していないため、引き続き機能します。 Objective-Cは、ルックアップテーブルを使用してクラス名をアドレスにマッピングします。これは1対1のマッピングであるため、同じ名前のクラスを2つ持つことはできません。したがって、両方のクラスをロードするには、一方のクラスの名前を変更する必要があります。ただし、他のクラスがその名前のクラスの1つにアクセスする必要がある場合、ルックアップテーブルにそのアドレスを要求し、ルックアップテーブルは元のクラスの名前が指定された名前変更されたクラスのアドレスを決して返しません。

47
Michael Buckley

クラスに一意のプレフィックスを付けることは基本的に唯一のオプションですが、これを面倒で見苦しいものにする方法はいくつかあります。オプションについての長い議論があります here 。私のお気に入りは@compatibility_alias Objective-Cコンパイラ指令です( here で説明)。 @compatibility_aliasを使用してクラスの「名前を変更」し、FQDNまたはそのようなプレフィックスを使用してクラスに名前を付けることができます。

@interface COM_WHATEVER_ClassName : NSObject
@end

@compatibility_alias ClassName COM_WHATEVER_ClassName
// now ClassName is an alias for COM_WHATEVER_ClassName

@implementation ClassName //OK
//blah
@end

ClassName *myClass; //OK

完全な戦略の一環として、すべてのクラスにFQDNなどの一意のプレフィックスを付けてから、すべての@compatibility_aliasを含むヘッダーを作成できます(このヘッダーを自動生成できると思います)。

このようなプレフィックスのデメリットは、コンパイラ以外の文字列からのクラス名を必要とするものには、真のクラス名(上記のCOM_WHATEVER_ClassNameなど)を入力する必要があることです。特に、@compatibility_aliasはランタイム関数ではなくコンパイラ指令であるため、NSClassFromString(ClassName)は失敗します(nilを返します)。NSClassFromString(COM_WHATERVER_ClassName)を使用する必要があります。ビルドフェーズでibtoolを使用して、Interface Builder nib/xibでクラス名を変更し、Interface Builderで完全なCOM_WHATEVER _...を記述する必要がないようにすることができます。

最後の警告:これはコンパイラー指示(およびその点であいまいな指示)であるため、コンパイラー間で移植できない場合があります。特に、LLVMプロジェクトのClangフロントエンドで動作するかどうかはわかりませんが、LLVM-GCC(GCCフロントエンドを使用するLLVM)で動作するはずです。

93
Barry Wark

問題を解決するのに役立つかもしれないトリッキーで賢いコードをすでにいくつかの人々が共有しています。いくつかの提案は機能する可能性がありますが、それらはすべて理想的とは言えず、実装するのは実に厄介です。 (omeいハックは避けられないこともありますが、できる限り回避するようにしています。)実用的な観点から、ここに私の提案があります。

  1. いずれの場合でも、開発者にbothフレームワークの競合を通知し、それを回避および/または対処できないことがあなたを引き起こしていることを明確にします実際のビジネス上の問題。未解決の場合、ビジネスの損失につながる可能性があります。クラスごとに既存の競合を解決するのはそれほど邪魔にならない修正ですが、プレフィックスを完全に変更する(または、現在存在しない場合はプレフィックスを使用し、恥をかかせない)ことを保証する最善の方法であることを強調します同じ問題をもう一度見てください。
  2. 命名の競合がかなり小さなクラスのセットに制限されている場合、特に競合するクラスの1つが直接的または間接的にコードで使用されていない場合、それらのクラスのみを回避できるかどうかを確認してください。その場合、競合するクラスを含まないフレームワークのカスタムバージョンをベンダーが提供するかどうかを確認してください。そうでない場合、その柔軟性がフレームワークの使用によるROIを低下させているという事実について率直に言ってください。理由の範囲内で強引であることを気にしないでください-顧客は常に正しいです。 ;-)
  3. あるフレームワークがより「不可欠」である場合、サードパーティまたは自作の別のフレームワーク(またはコードの組み合わせ)で置き換えることを検討できます。 (後者は、開発と保守の両方で追加のビジネスコストが確実に発生するため、望ましくない最悪のケースです。)その場合、フレームワークを使用しないことにした理由をベンダーに正確に伝えてください。
  4. 両方のフレームワークがアプリケーションに等しく不可欠であると考えられる場合は、そのうちの1つの使用を1つ以上の個別のプロセスに分解する方法を検討してください。コミュニケーションの程度によっては、これは予想したほど悪くないかもしれません。いくつかのプログラム(QuickTimeを含む)はこのアプローチを使用して、 Leopardのシートベルトサンドボックスプロファイル を使用することで提供されるよりきめ細かいセキュリティを提供します。これにより、コードの特定のサブセットのみが重要または機密操作を実行できます。パフォーマンスはトレードオフになりますが、唯一の選択肢かもしれません

ライセンス料、条件、および期間がこれらのポイントのいずれかでの即時アクションを妨げる可能性があると推測しています。できるだけ早く競合を解決できることを願っています。がんばろう!

12
Quinn Taylor

これは大したことですが、 分散オブジェクト を使用して、下位プログラムのアドレスとRPCにのみクラスの1つを保持することができます。大量のものをやり取りしている場合は、面倒になります(両方のクラスがビューを直接操作している場合などは不可能な場合があります)。

他の潜在的な解決策もありますが、それらの多くは正確な状況に依存しています。特に、最新のランタイムまたはレガシーランタイムを使用していますか、ファットまたはシングルアーキテクチャ、32ビットまたは64ビット、対象OSリリース、動的リンク、静的リンク、または選択肢がありますか?新しいソフトウェアの更新のためにメンテナンスが必要になる可能性があることをしても大丈夫です。

本当に必死なら、あなたができることは:

  1. ライブラリの1つに対して直接リンクしない
  2. ロード時に名前を変更するobjcランタイムルーチンの代替バージョンを実装します( objc4 プロジェクトをチェックアウトします。正確に何をする必要があるかは、上記の質問の数によって異なりますが、答えが何であっても可能です)。
  3. mach_override のようなものを使用して、新しい実装を注入します
  4. 通常のメソッドを使用して新しいライブラリをロードすると、パッチが適用されたリンカールーチンを通過し、そのclassNameが変更されます

上記はかなり手間がかかります。複数のarchと異なるランタイムバージョンに対して実装する必要がある場合は非常に不快になりますが、確実に機能させることができます。

8
Louis Gerbarg

ランタイム関数(/usr/include/objc/runtime.h)を使用して競合するクラスの1つを非衝突クラスに複製し、衝突クラスフレームワークをロードすることを検討しましたか? (これを行うには、衝突するフレームワークを異なる時間にロードして動作させる必要があります。)

ランタイムでクラスivar、メソッド(名前と実装アドレスを含む)、および名前を検査し、同じivarレイアウト、メソッド名/実装アドレスを持ち、名前のみが異なるように独自に動的に作成することができます(衝突)

4
xtophyr

絶望的な状況は必死の手段を必要とします。ライブラリの1つのオブジェクトコード(またはライブラリファイル)をハッキングし、衝突するシンボルを、同じ長さで異なるスペル(ただし、推奨、同じ長さの名前)の代替名に変更することを検討しましたか?本質的に厄介です。

コードが同じ名前で実装が異なる2つの関数を直接呼び出しているかどうか、または競合が間接的であるかどうかは明確ではありません(違いがあるかどうかも明確ではありません)。ただし、少なくとも名前の変更が機能する外部の可能性があります。スペルの違いを最小限に抑えることも考えられるでしょう。シンボルがテーブル内で並べ替えられた順序にある​​場合、名前の変更によって順序が変わることはありません。バイナリ検索のようなものは、検索している配列が期待どおりに並べ替えられていない場合、動揺します。

3

@compatibility_aliasは、クラスの名前空間の競合を解決できます。

@compatibility_alias NewAliasClass OriginalClass;

ただし、これは、enum、typedef、またはプロトコル名前空間の衝突を解決しません。さらに、それは@class元のクラスの転送宣言。ほとんどのフレームワークにはtypedefなどのこれらの非クラスのものが付属するため、compatibility_aliasだけでは名前空間の問題を修正できない可能性があります。

あなたと同様の問題 を見ましたが、ソースにアクセスでき、フレームワークを構築していました。このために私が見つけた最良の解決策は、@compatibility_alias条件付きで#definesを使用して、enums/typedefs/protocols/etcをサポートします。問題のヘッダーのコンパイルユニットで条件付きでこれを行うことで、他の衝突するフレームワークでデータを拡張するリスクを最小限に抑えることができます。

1
Michael Chinen

問題は、同じ翻訳単位(ソースファイル)で両方のシステムのヘッダーファイルを参照できないことです。ライブラリにObjective-Cラッパーを作成し(プロセスでより使いやすくする)、ラッパークラスの実装に各ライブラリのヘッダーのみを含めると、名前の衝突を効果的に分離できます。

Objective-C(これはまだ始まったばかりです)でこれに関する十分な経験はありませんが、私はCでそれを行うと信じています。

1
chrish

ただの考え..テストも証明もされておらず、マークの方法である可能性がありますが、使用しているクラスのアダプターを、より単純なフレームワークから作成することを検討しましたか。

シンプルなフレームワーク(または、アクセス頻度が最も低いフレームワーク)のラッパーを作成する場合、そのラッパーをライブラリにコンパイルすることはできません。ライブラリがプリコンパイルされており、itsヘッダーのみを配布する必要がある場合、基礎となるフレームワークを効果的に非表示にして、衝突して2番目のフレームワークと自由に組み合わせることができます。

もちろん、両方のフレームワークのクラスを同時に使用する必要がある場合がありますが、そのフレームワークのさらに別のクラスアダプターのファクトリーを提供することもできます。その時点で、両方のフレームワークから使用しているインターフェイスを抽出するには、リファクタリングが少し必要になると思います。これは、ラッパーを構築するための素敵な出発点を提供するはずです。

ラップされたライブラリからさらに機能を必要とするときに、ライブラリに基づいてビルドし、変更されたときに単純に再コンパイルできます。

繰り返しますが、決して証明されていませんが、視点を追加するように感じました。それが役に立てば幸い :)

0
mark

衝突が発生した場合、フレームワークの1つをアプリケーションからリファクタリングする方法についてよく考えることをお勧めします。衝突があるということは、この2つが同じようなことをしていることを示しており、アプリケーションをリファクタリングするだけで、追加のフレームワークを使用して回避できる可能性があります。これにより、名前空間の問題が解決されるだけでなく、コードの堅牢性、保守性、効率性が向上します。

より技術的な解決策よりも、私があなたの立場にあれば、これが私の選択でしょう。

0
Allyn

ファイルのプレフィックスは、私が知っている最も簡単な解決策です。 Cocoadevには、名前空間の衝突を避けるためのコミュニティの取り組みである名前空間ページがあります。このリストにあなた自身を自由に追加してください、私はそれが何のためであると信じています。

http://www.cocoadev.com/index.pl?ChooseYourOwnPrefix

0
Ryan Townshend

衝突が静的リンクレベルでのみ発生する場合、シンボルの解決に使用するライブラリを選択できます。

cc foo.o -ldog bar.o -lcat

foo.oおよびbar.o両方ともシンボルratを参照し、libdogが解決しますfoo.oratlibcatbar.orat

0
wcochran