Modern Runtimeでのインスタンス変数の使用
-
06-07-2019 - |
質問
Obj-cとCocoaで数年の経験がありますが、今では、Obj-C 2.0などの進歩に戻っています。
私は、最新のランタイムに頭を突っ込んで、プロパティなどを宣言しようとしています。少し混乱するのは、最新のランタイムでiVarを暗黙的に作成できることです。そしてもちろん、これは、コード内で値にアクセスするには常にself.propertyを使用する必要があることを意味します。
ただし、init *およびdealloc(GCを使用しない場合)メソッドでは、iVarを(現在のランタイムで)直接使用する必要があります。
質問は次のとおりです。
-
Moden Runtimeでは、init *およびdeallocでプロパティアクセサーを使用する必要がありますか?
-
そうである場合 、なぜこれが違うのですか?コンパイラがiVarを認識できないからですか?
-
アクセサーをオーバーライドする必要がある場合、実行時に定義されるiVarにアクセスできますか、それともランタイムが使用する実際のiVarを定義する必要がありますか?
-
もう一度、 合成されたiVarにアクセスできる場合 、init *およびdeallocメソッドに対してこれを続けられないのはなぜですか?
ドキュメントを数回読んでいますが、これらすべてについて少し曖昧に思えたので、コーディングを継続する方法を決定するために、それをよく理解しておく必要があります。
私の質問が明確であることを願っています。
テストの簡単な要約:
-
レガシーでivarを宣言しない場合、コンパイラは完全に不満です
-
レガシーコンパイラのivarの周りで
#ifndef __OBJC2 __
を使用する場合は満足であり、ivarを直接およびプロパティとして使用できます -
最新のランタイムでは、ivarを未定義のままにしてプロパティとしてアクセスできます
-
最新のランタイムでは、宣言せずにivarに直接アクセスしようとすると、コンパイル中にエラーが発生します
-
@private
ivarの宣言は、もちろん、レガシーとモダンの両方でivarへの直接アクセスを許可します
今、本当に前に進むためのきれいな方法を与えていないのですか?
解決
現在の(OS X 10.5 / GCC 4.0.1)コンパイラーでは、ランタイム合成ivarに直接アクセスできません。 OS Xランタイムエンジニアの1人であるGreg Parkerが cocoa-devリストでの方法(2009年3月12日):
現在のコンパイラでは使用できません。 A 将来のコンパイラはそれを修正する必要があります。つかいます の明示的な@private ivar その間。 @private ivarは 契約の一部とみなされる- それが@privateの意味であり、強制されています コンパイラの警告とリンカによる エラー。
そして、なぜする方法がないのですか インスタンス変数を明示的に宣言する 新しいランタイムの.mファイルにありますか?
3つの理由:(1)いくつかあります 機能する重要な設計の詳細 out、(2)コンパイラーエンジニア時間は (3)@private ivarは 一般的には十分です。
したがって、現時点では、 init
および dealloc
であっても、ドット表記を使用してプロパティにアクセスする必要があります。これは、これらの場合にivarを直接使用するベストプラクティスに反しますが、回避する方法はありません。ほとんどの場合、ランタイム合成ivarの使いやすさ(およびパフォーマンス上の利点)がこれを上回ることがわかります。 ivarに直接アクセスする必要がある場合は、Greg Parkerが提案するように@private ivarを使用できます(明示的に宣言されたivarとランタイム合成されたivarを混在させることを妨げるものはありません)。
更新 OS X 10.6では、64ビットランタイムにより、 self-> ivar
を介して合成ivarに直接アクセスできます。
他のヒント
インスタンス変数自体は最新のランタイムでのみ合成できるため(また、32ビットまたはpre-Leopardの@interfaceで宣言する必要があります)、ivarを宣言するのが最も安全で移植性が高い
- Modern Runtimeでは、init *およびdeallocでプロパティアクセサーを使用する必要がありますか
私の経験則は「おそらく」です。 -init *
および"通常not"の場合 -dealloc
の場合。
オブジェクトを初期化するときは、ivarの値を適切にコピー/保持するようにしてください。プロパティのセッターに初期化に不適切な副作用がなければ、プロパティが提供する抽象化を確実に再利用してください。
オブジェクトの割り当てを解除するとき、ivarオブジェクトを解放しますが、新しいオブジェクトは保存しません。これを行う簡単な方法は、プロパティをnil( myObject.myIvar = nil
)に設定することです。これは基本的に [myObject setMyIvar:nil]
を呼び出します。 nilへのメッセージは無視されるため、これには危険はありません。ただし、[myIvar release]の場合はやり過ぎです。通常、必要なのはこれだけです。一般に、割り当て解除が変数の設定と異なる動作をする必要がある状況では、プロパティ(または直接、セッター)を使用しないでください。
init / deallocでプロパティアクセサを使用することに対するeJamesの議論は理解できますが、プロパティの動作を変更すると(たとえば、保持からコピーへの変更、または保持せずに単に割り当てる)、初期化で使用したり、その逆を行ったりすると、動作が同期しなくなる可能性があります。 ivarの初期化と変更が同じように動作する場合は、両方にプロパティアクセサーを使用します。
- もしそうなら、なぜこれが違うのですか?コンパイラがivarを認識できないからですか?
最新のランタイムは、クラスのサイズとレイアウトをよりインテリジェントに処理するため、サブクラスを再コンパイルせずにivarのレイアウトを変更できます。また、対応するプロパティの名前とタイプから、必要なivarの名前とタイプを推測することもできます。 Objective-C 2.0ランタイムプログラミングガイドには詳細がありますが、そこに詳細がどれほど深く説明されているかわかりません。
- アクセサーをオーバーライドする必要がある場合、実行時に定義されるiVarにアクセスできますか、それともランタイムが使用する実際のiVarを定義する必要がありますか?
これはテストしていませんが、実際には作成する必要があるため、名前付きのivarにコードでアクセスすることが許可されていると思います。コンパイラーが文句を言うかどうかはわかりませんが、文句を言わずにivarを合成できるので、合成されたivarについて知って、名前で参照できるようになっていると思います。
- 繰り返しますが、合成されたiVarにアクセスできるのに、init *およびdeallocメソッドに対してこれを続けられないのはなぜですか?
インスタンスが割り当てられた後は、いつでもプロパティやivarにアクセスできるはずです。
同様の情報を持つ別のSO質問ですが、それはまったく重複していません。
Objective-C 2.0ドキュメントの最終行、および Mark Besseyの回答は次のとおりです。
ランタイムに依存する動作に違いがあります(“ランタイムの違い”も参照):
レガシーランタイムの場合、インスタンス変数は@interfaceブロックで既に宣言されている必要があります。プロパティと同じ名前で互換性のあるタイプのインスタンス変数が存在する場合、それが使用されます。そうでない場合、コンパイラエラーが発生します。
最新のランタイムでは、インスタンス変数は必要に応じて合成されます。同じ名前のインスタンス変数が既に存在する場合は、それが使用されます。
私の理解は次のとおりです。
プロパティアクセサを init *
および dealloc
メソッドで使用しないでください。これは、レガシーランタイムでプロパティアクセサを使用してはならないのと同じ理由からです。後でプロパティメソッドをオーバーライドし、 init *
または dealloc
で実行してはいけないことを行うと、潜在的なエラーが発生します。
ivarを合成し、次のようにプロパティメソッドをオーバーライドする ことができるはずです:
@interface SomeClass
{
}
@property (assign) int someProperty;
@end
@implementation SomeClass
@synthesize someProperty; // this will synthesize the ivar
- (int)someProperty { NSLog(@"getter"); return someProperty; }
- (void)setSomeProperty:(int)newValue
{
NSLog(@"setter");
someProperty = newValue;
}
@end
これは、 init *
および dealloc
メソッドでも合成されたivarにアクセスできると思うように導きます。私が考えることができる唯一の落とし穴は、 init *
および deallocの定義の
行が に来る必要があるかもしれないということですソースファイルのメソッド。
最終的には、インターフェイスで宣言されたivarが引き続き機能するため、これが最も安全な方法です。
同じ問題に直面しています。合成されたインスタンス変数にアクセスできないことを回避する方法は次のとおりです。
公開ヘッダー
@interface MyObject:NSObject {
}
@property (retain) id instanceVar;
@property (retain) id customizedVar;
@end
プライベートヘッダー/実装
@interface MyObject()
@property (retain) id storedCustomizedVar;
@end
@implementation MyObject
@synthesize instanceVar, storedCustomizedVar;
@dynamic customizedVar;
- customizedVar {
if(!self.storedCustomizedVar) {
id newCustomizedVar;
//... do something
self.storedCustomizedVar= newCustomizedVar;
}
return self.storedCustomizedVar;
}
- (void) setCustomizedVar:aVar {
self.storedCustomizedVar=aVar;
}
@end
それほどエレガントではありませんが、少なくとも公開ヘッダーファイルはきれいに保たれます。
KVOを使用する場合は、storedCustomizedVarの依存キーとしてcustomizedVarを定義する必要があります。
私はObj-Cには比較的新しい(ただし、プログラミングは初心者)ので、このトピックで混乱しています。
心配な点は、プロパティの代わりに不注意でiVarを使用するのは比較的簡単だと思われることです。例:
myProp = someObject;
の代わりに
self.myProp = someObject;
確かにこれは「ユーザー」です。エラーですが、いくつかのコードで誤って行うのはまだ簡単なようです。保持されたプロパティまたはアトミックプロパティの場合、おそらく問題につながる可能性があります。
理想的には、 any iVarを生成するときに、ランタイムがプロパティ名に何らかのパターンを適用できるようにしたいと思います。例えば。常に" _"をプレフィックスとして付けます。
実際には、これを手動で実行しています-明示的にivarを宣言し、意図的にプロパティとは異なる名前を付けます。古いスタイルの「m」プレフィックスを使用しているため、私のプロパティが「myProp」の場合、iVarは「mMyProp」になります。次に、@ synthesize myProp = mMyPropを使用して2つを関連付けます。
これは私が認める少し不器用で、少し余分なタイピングがありますが、コード内でもう少し明確にすることができるのは価値があるようです。もちろん、私はそれを間違って得ることができ、mmyProp = someObjectと入力できますが、「m」プレフィックスがエラーを警告してくれることを期待しています。
プロパティを宣言して、コンパイラ/ランタイムに残りの処理をさせればもっと気分が良くなりますが、多くのコードがある場合、直感が直感的であるので、まだしなければならない場合は間違いを犯すと教えてくれますinit / deallocの手動ルールに従います。
もちろん、私が間違っていることもできる他のこともたくさんあります...