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


174

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

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

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

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

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


更新:

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


7
あなたは良い質問があります(名前の衝突がある2つのフレームワークが必要な場合はどうするか)が、それはテキストに埋もれています。明確にするために修正してください。そうすれば、今あるような単純な答えは避けられます。
ベンザド2008年

4
これは、Objective-C言語の現在の設計に対する私の最大の不満です。以下の答えを見てください。問題に実際に対処するもの(NSBundleのアンロード、DOの使用など)は、名前空間の競合を回避するほどの些細なことには必要ないはずの恐ろしいハックです。
erikprice 2009年

@erikprice:アーメン。私はobj-cを学んでいて、まさにこの問題に直面しています。簡単な解決策を探してここに来た……ラメ。
Dave Mateer、

1
記録のために、技術的にはCとObjective-Cの両方が複数の名前空間のサポートを提供していますが、OPが求めているものとは厳密には一致していません。参照してくださいobjectivistc.tumblr.com/post/3340816080/...

うーん、知りませんでした。ひどい設計上の決定のようなものですか?
ニコ

回答:


47

両方のフレームワークのクラスを同時に使用する必要がなく、NSBundleのアンロード(OS X 10.4以降、GNUStepのサポートなし)をサポートするプラットフォームを対象としていて、パフォーマンスは実際には問題ではない場合、クラスを使用する必要があるたびに1つのフレームワークをロードし、それをアンロードして、他のフレームワークを使用する必要があるときに他のフレームワークをロードすることができます。

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

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

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


5
バンドルのアンロードは10.5以降までサポートされていないと思います。
クインテイラー

93

クラスに一意のプレフィックスをプレフィックスすることが基本的に唯一のオプションですが、これを煩わしくなく見苦しくする方法はいくつかあります。ここでオプションについての長い議論があります。私のお気に入りは、@compatibility_aliasObjective-Cコンパイラディレクティブです(ここで説明)。を使用@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)で動作するはずです。


4
compatibility_aliasに賛成票を投じ、私はそれについて知りませんでした!ありがとう。しかし、それは私の問題を解決しません。私は、使用しているいずれかのフレームワークのプレフィックスを変更することはできません。バイナリ形式でのみ存在し、衝突します。それについて私は何をすべきですか?
メッキ

@compatibility_alias行はどのファイル(.hまたは.m)に移動しますか?
Alex Basson、2010

1
@Alex_Basson @compatibility_aliasは、@ interface宣言が表示されている場所に表示されるように、ヘッダーに含める必要があります。
Barry Wark 2010

@compatibility_aliasをプロトコル名、typedef定義、または他の何かで使用できるかどうか疑問に思いますか?
アリ

ここで良い考え。メソッドのスウィズリングを使用して、NSClassFromString(ClassName)がエイリアスクラスに対してnilを返さないようにすることはできますか?クラス名をとるすべてのメソッドを見つけるのはおそらく難しいでしょう。
Dickey Singh

12

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

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

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


8

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

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

あなたが本当に絶望的であるなら、あなたができることは:

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

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


4

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

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


3

絶望的な状況では、絶望的な対策が必要です。ライブラリの1つのオブジェクトコード(またはライブラリファイル)をハッキングして、衝突するシンボルを別の名前に変更しました-同じ長さでスペルが異なります(ただし、推奨事項は同じ長さの名前)。本質的に厄介。

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


ライセンスでこれが許可されていないため、ディスク上のライブラリを変更することは問題外です。メモリ内のシンボルを変更することは可能ですが、これを行う方法はありません(libをメモリにロードし、変更してから動的リンカーに渡します...そのためのメソッドはありません)。
メッキー

3
OK-2つのライブラリのどちらがそれほど重要ではないかを判断し、代わりのものを見つける時間です。そして、あなたが支払うものに、あなたが変更している理由がこの衝突によるものであり、彼らがあなたの問題を修正することによってあなたのビジネスを維持できることを放棄していることを説明します。
ジョナサンレフラー、

2

@compatibility_alias クラスの名前空間の競合を解決できるようになります。

@compatibility_alias NewAliasClass OriginalClass;

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

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


1

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

私はこれをObjective-Cで十分に経験していません(始めたばかりです)が、Cでそれを行うと思います。


1
しかし、同じファイルに両方のラッパーヘッダーを含めようとすると、衝突が発生しませんか(それぞれがそれぞれのフレームワークのヘッダー自体を含める必要があるため)。
Wilco、

0

ファイルのプレフィックスは、私が知っている最も簡単なソリューションです。Cocoadevには、ネームスペースの衝突を回避するためのコミュニティの取り組みであるネームスペースページがあります。このリストに自由に追加してください。それが目的です。

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


ファイル内で定義されたオブジェクトが問題を引き起こす場合、ファイルのプレフィックスはどのように役立ちますか?私は確かにhファイルを操作することができますが、フレームワークに好意を示すとき、リンカーはオブジェクトをもう見つけません:-/
Mecki

これは、最初の問題の解決策というよりはむしろ最良の実践法になると思います。
アリ

これは質問にまったく対処しません
Madbreaks

0

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

より技術的な解決策よりも、私があなたの立場にいた場合、これが私の選択です。


0

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

cc foo.o -ldog bar.o -lcat

場合foo.obar.oの両方の参照記号はrat、その後libdog解決されますfoo.oratlibcat解決しますbar.oのをrat


0

ただ考えてみました。テストも証明もされていないので、目印になるかもしれませんが、フレームワークの単純なものから、または少なくともそれらのインターフェイスから、使用するクラスのアダプタを書くことを検討しましたか?

単純なフレームワーク(または、インターフェースへのアクセスが最も少ないフレームワーク)のラッパーを記述する場合、そのラッパーをライブラリーにコンパイルすることはできません。ライブラリがプリコンパイルされており、そのヘッダーのみを配布する必要がある場合、基盤となるフレームワークを効果的に隠蔽し、衝突する2番目のフレームワークと自由に組み合わせることができます。

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

必要に応じて、ラップされたライブラリからさらに機能が必要になったときにライブラリを構築し、変更されたときに再コンパイルすることができます。

繰り返しになりますが、証明されたわけではありませんが、視点を追加するような気がしました。それが役に立てば幸い :)


-1

同じ関数名を持つ2つのフレームワークがある場合は、フレームワークを動的にロードしてみることができます。洗練されていませんが、可能です。Objective-Cクラスでそれを行う方法、私は知りません。私は推測しているNSBundleクラスは、特定のクラスをロードしますメソッドを持っています。

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