3

Core Data、宣言されたプロトコル、およびおそらく LLVM 1.5 コンパイラに関連する奇妙な癖に遭遇しています。これが状況です。

IPContainer が IPEvent の親エンティティである IPContainer と IPEvent の 2 つのクラスを持つ Core Data モデルがあります。各エンティティには、mogenerator を使用して作成されたプロジェクト内のカスタム クラスがあります。mogenerator は、モデル化されたプロパティ宣言だけを含む追加のサブクラスを生成するため、実際のクラス階層は IPEvent > _IPEvent > IPContainer > _IPContainer > NSManagedObject です。IPContainer エンティティには、@property(nonatomic, retain) NSNumber* id;_IPContainer.h で宣言されている「id」という名前の属性があります。_IPContainer.m は@dynamic id;実装にあり、コア データに実行時にアクセサーを生成するように指示します。

また、プロジェクトで宣言されたプロトコル IPGridViewGroup があり、いくつかのプロパティを定義します。そのうちの 1 つは同じ「id」プロパティです。ただし、このプロトコルを実装するクラスにはセッターは必要ないため、プロトコル内のプロパティは次のように宣言され@property(readonly) NSNumber* id; ます。 IPEvent クラスは、IPGridViewGroup プロトコルに準拠していることを宣言します。

これは、Clang/LLVM 1.0.x コンパイラ (Xcode 3.2.2 に同梱されているバージョン) を使用すると問題なく動作しましたが、Xcode 3.2.3 と Clang/LLVM 1.5 にアップグレードすると、さまざまなことが変わりました。まず、IPEvent クラスのコンパイル時に次の警告が表示されます。

/Volumes/Ratbert/Users/bwebster/Projects/UberProject/iPhotoLibraryManager/IPGridViewGroup.h:19:31: warning: property 'id' requires method 'id' to be defined - use @synthesize, @dynamic or provide a method implementation

次に、実際にプログラムを実行すると、コンソールに次のように出力されます。

Property 'id' is marked readonly on class 'IPEvent'.  Cannot generate a setter method for it.

すぐに続きます:

-[IPEvent setId:]: unrecognized selector sent to instance 0x200483900

また、IPEvent クラスでプロパティを再宣言しようとしましたが、別のコンパイラ警告が表示され、実行時に同じ動作が発生しました。

/Volumes/Ratbert/Users/bwebster/Projects/UberProject/iPhotoLibraryManager/IPManagedObject/IPEvent.h:14:40: warning: property 'id' 'retain' attribute does not match the property inherited from 'IPGridViewGroup'

ここで変更されたのはコンパイラだけなので、変更のきっかけは明らかですが、これが新しいバージョンのコンパイラのバグと見なされるのか、それとも古いバージョンのコンパイラは実際には正しく動作していませんでしたが、新しいバージョンでは、バグがあるのは自分のコードであることが明らかになりました。

だから私がここに持っている質問の中には次のものがあります:

  1. クラスが読み取り専用プロパティを持つプロトコルに準拠していても問題ないように思われますが、独自の実装でプロパティに読み取り書き込みアクセスを提供しますが、それは正しいですか? ただし、ここでの癖は、 readwrite プロパティが実際には、プロトコルに準拠するクラスのスーパークラスで宣言されていることです。
  2. コンソール メッセージが Core Data の内部のどこかに出力されていると想定しています。ただし、IPGridViewGroup プロトコルに準拠する場合を除き、IPEvent 自体は「id」プロパティを明示的に宣言しないため、これは奇妙です。ただし、この場合、コンパイラエラーが発生すると思います。これは、AFAIKが通常許可されていない同じプロパティの読み取り専用バージョンで読み取り書き込みプロパティ(_IPContainerスーパークラスで宣言されている)を効果的にオーバーライドするためです。.
  3. これがコンパイラのバグであれば問題ありません。今のところ、いくつかの異なる方法で回避できます。ただし、コンパイラがここで正しいことを行っている場合、コンパイラの警告や実行時エラーが発生しないように、これらすべてを整理する方法を考え出すのに途方に暮れています。

編集:回避策は、IPEvent クラスでプロパティを再度宣言することですが、コンパイラの 2 つのバージョンの動作が異なる理由についてはまだ戸惑っています。また、プロトコルで宣言されたプロパティが、クラスで宣言されたプロパティとどのように相互作用するかについても不明です。

クラスで (プロトコルではなく) 読み取り専用プロパティを宣言し、読み取り書き込みプロパティをオーバーライドすると、「警告: プロパティ 'longitude' の属性 'readonly' は、'_IPEvent' から継承されたプロパティの属性 'readwrite' を制限します」というメッセージが表示されます。プロトコルで宣言しても同じ効果がある場合は、コンパイラから同様の警告が表示されるはずです。

ただし、直感的には、IPEvent はプロパティに必要なゲッターを既に実装しているため、たまたまプロパティのセッターも実装していたとしても、それは「プロトコルに準拠している」と見なされるはずです。

4

3 に答える 3

4

ここで変更されたのはコンパイラだけなので、変更のきっかけは明らかですが、これが新しいバージョンのコンパイラのバグと見なされるのか、それとも古いバージョンのコンパイラは実際には正しく動作していませんでしたが、新しいバージョンでは、バグがあるのは自分のコードであることが明らかになりました。

新しいコンパイラは、同じクラスの同じインスタンス変数のアクセサに対して 2 つの別個の定義があることに気付きました。もちろん、リンカーは文句を言うべきです。

古いコンパイラはこれを元に戻す必要がありました。@property 宣言は、クラスまたはプロトコルで発生するかどうかに関係なく、暗黙的なメソッド宣言です。クラスとプロトコルの両方で同じ名前のプロパティを定義すると、1 つのクラスに対して 2 セットのメソッド宣言ができてしまいます。これは明らかにどこかで問題を引き起こします。

2 つのコンパイラの違いは、ソース内の#importステートメントの順序やソース ファイルの変更日など、些細なことかもしれません。

IPContainer クラスには 2 つの動的メソッド定義があり、1 つはセッターのみを生成し、もう 1 つはセッターとゲッターを生成するため、明らかに衝突が発生しています。コンパイラはどれを使用するかをどのように知る必要がありますか? 読み取り専用の読み取り書き込みプロパティが必要であることを伝えました。さらに悪いことに、これは動的プロパティであるため、実行時に実際に何が生成されるかわかりません。

1 クラスが読み取り専用プロパティを持つプロトコルに準拠していても問題ないように思われますが、独自の実装でそのプロパティに読み取り書き込みアクセスを提供しますが、それは正しいですか?

「OK」を定義します。コンパイラはそれを受け入れますか? おそらく。結局、プロトコルの readonly プロパティでは getter メソッドを定義しましたが、クラスでは setter メソッドも定義しました。プロトコルは、実装クラスが持つことができる追加メソッドを制限しないため、setter メソッドは、他の無関係なメソッドを追加できるのと同じように追加できます。

ただし、これは明らかに、特に NSManagedObject サブクラスの場合、非常に危険です。管理対象オブジェクトのコンテキストは、動作するクラスで何を見つけることが期待されるかについて、非常に確固たる期待を持っています。

2 ただし、IPGridViewGroup プロトコルに準拠する場合を除いて、IPEvent 自体は「id」プロパティを明示的に宣言しないため、これは奇妙です。

プロパティがプロトコルで必要な場合は、プロトコルを採用して明示的に宣言しています。

3 これがコンパイラのバグであれば問題ありません。今のところ、いくつかの異なる方法で回避できます。ただし、コンパイラがここで正しいことを行っている場合、コンパイラの警告や実行時エラーが発生しないように、これらすべてを整理する方法を考え出すのに途方に暮れています。

最も簡単な解決策は、(1) クラス プロパティと重複するプロトコルを定義しないことです。そうすることは、とにかくプロトコルを持つという目的全体を無効にします。(2) プロトコル プロパティを readwrite にして、コンパイラとランタイムが混乱しないようにします。

ただし、直感的には、IPEvent はプロパティに必要なゲッターを既に実装しているため、たまたまプロパティのセッターも実装していたとしても、それは「プロトコルに準拠している」と見なされるはずです。

動的プロパティを使用していない場合は、おそらくそれを回避できます。動的プロパティを使用すると、コンパイラはランタイムに対して、その場で生成するアクセサーを説明するメッセージを生成する必要があります。この場合、何と言うべきでしょうか?「読み取り専用プロトコルに準拠したメソッドを生成しますが、同時に読み取り書き込みを行いますか?」

コンパイラが不平を言っているのも不思議ではありません。それが犬だったら、混乱しておもらしをしているだろう。

設計を真剣に考え直す必要があると思います。このような非標準的で危険な設計から、どのようなメリットが得られるでしょうか? コンパイラ エラーが発生するのが最良のシナリオです。最悪の場合、ランタイムは予測不能な結果で混乱します。

要するに、(シェイクスピアに謝罪して)「...責任は準拠者にあるのではなく、私たち自身にあります。」

于 2010-06-27T15:11:20.783 に答える
0

これを少し分解してみましょう。私が正しく理解している場合:

  • IPEventを継承_IPEventして実装するクラスIPGridViewGroupです。
  • IPGridViewGroupreadonlyプロパティがありますid
  • _IPEventからreadwriteプロパティを継承します。id_IPContainer

それらの仮定が正しいと仮定すると(そして私が間違っているかどうか教えてください)、 2つの異なるプロパティIPEventを継承しました。1つはそうであり、もう1つはそうではありません。idreadonly

明示的な修飾子を使用してidプロパティを再定義しようとしましたか?IPEventreadwrite

元:

@property (nonatomic, retain, readwrite) NSNumber *id;

うまくいけば、コンパイラはヒントを取得してセッターを生成します。

于 2010-06-24T02:25:44.850 に答える
0
@property(readonly) NSNumber* id

正しくないようです。Core Data のドキュメントには、非アトミックを使用する必要があると記載されています (ここではスレッドを使用できないため)。ID はオブジェクトであるため、割り当てるのではなく保持する必要があります (デフォルト)。

サブクラスがスーパークラスの ivar にアクセスする必要がある場合は、そのプロパティを宣言し、@dynamic を使用してコンパイラに静かにするように指示する必要があります。あなたがそうしているようには見えません。

また、コンパイラによって異なることがわかったこのバグに関連している可能性もあります。

http://openradar.appspot.com/8027473 ivar のないプロップが宣言されている場合、コンパイラはスーパークラス ivar の存在を忘れる

Core Data では id が特別な意味を持つ可能性があり、別の名前を使用する必要があります。

于 2010-06-24T02:48:20.993 に答える