Objective-Cの読み取り専用プロパティ?


93

私は自分のインターフェイスで次のように読み取り専用プロパティを宣言しました:

 @property (readonly, nonatomic, copy) NSString* eventDomain;

プロパティを誤解しているかもしれませんが、として宣言するとreadonly、実装(.m)ファイル内で生成されたセッターを使用できますが、外部エンティティは値を変更できないと思いました。このSOの質問は、それが起こるべきことだと言っています。それが私が求めている行動です。ただし、標準のセッターまたはドット構文を使用しeventDomainてinitメソッド内に設定しようとすると、unrecognized selector sent to instance.エラーが発生します。もちろん私は@synthesizeその物件を所有しています。次のように使用しようとしています:

 // inside one of my init methods
 [self setEventDomain:@"someString"]; // unrecognized selector sent to instance error

だから私readonlyはプロパティの宣言を誤解していますか?または何か他のことが起こっていますか?

回答:


116

セッターも必要であることをコンパイラーに通知する必要があります。一般的な方法は、それを.mファイルのクラス拡張子に配置することです。

@interface YourClass ()

@property (nonatomic, copy) NSString* eventDomain;

@end

22
カテゴリではありません。これはクラス拡張です(エイコが言ったように)。同じ目的で使用されていますが、それらは明らかに異なります。たとえば、上記を真のカテゴリで行うことはできません。
bbum

2
クラス拡張プロパティを持つクラスから継承するクラスは、それらを表示しないことに気づきました。つまり、継承は外部インターフェイスのみを参照します。確認?
Yogev Shelly

5
それはかなり公平な話ではありません。それは異なる場合がありますが、「匿名カテゴリ」として知られています
MaxGabriel '10 / 10/15

3
これは、公開インターフェースでのopの最初の宣言に追加されますか?つまり、このクラス拡張宣言は、.h?それ以外の場合、これがパブリックセッターを公開する方法はわかりません。ありがとう
Madbreaks

4
@Madbreaks追加ですが、.mファイルにあります。そのため、コンパイラーはそのクラスに対してそれを読み取り書き込みにすることを知っていますが、外部使用は依然として期待どおり読み取り専用に制限されています。
Eiko

38

英子らが正解した。

簡単な方法は次のとおりです。プライベートメンバー変数に直接アクセスします。

ヘッダーの.hファイル:

@property (strong, nonatomic, readonly) NSString* foo;

実装.mファイル:

// inside one of my init methods
self->_foo = @"someString"; // Notice the underscore prefix of var name.

それだけです、それだけで十分です。ムスも大騒ぎもない。

細部

Xcode 4.4およびLLVMコンパイラ4.0(Xcode 4.4の新機能)以降、他の回答で説明されている雑用をいじる必要はありません。

  • synthesizeキーワード
  • 変数を宣言する
  • 実装.mファイルでプロパティを再宣言します。

プロパティを宣言した後foo、Xcodeがアンダースコアのプレフィックスで名前が付けられたプライベートメンバー変数を追加したと想定できます_foo

プロパティが宣言されている場合readwrite、Xcodeはというゲッターメソッドfooとというセッターを生成しsetFooます。これらのメソッドは、ドット表記(my Object.myMethod)を使用すると暗黙的に呼び出されます。プロパティが宣言された場合、readonlyセッターは生成されません。つまり、下線付きのバッキング変数自体は読み取り専用ではありませんreadonly何セッターメソッドは、値を設定するために、ドット表記法を使用し、したがって合成されず、単にたことを意味し、コンパイラエラーで失敗します。存在しないメソッド(セッター)をコンパイラーが呼び出せないため、ドット表記は失敗します。

これを回避する最も簡単な方法は、アンダースコアで名前が付けられたメンバー変数に直接アクセスすることです。アンダースコアの名前が付いた変数を宣言しなくても、そうすることができます。Xcodeはビルド/コンパイルプロセスの一部としてその宣言を挿入しているため、コンパイルされたコードには実際に変数宣言があります。ただし、元のソースコードファイルでその宣言を確認することはできません。魔法ではなく、単に構文上の砂糖です。

を使用するself->と、オブジェクト/インスタンスのメンバー変数にアクセスできます。これを省略して、var名を使用することもできます。しかし、自分のコードを自己文書化するので、私はself + arrowを使用することを好みます。あなたが見たときself->_foo、あなたは曖昧さなくて知っている_fooこのインスタンスのメンバ変数です。


ところで、ダイレクトIVARアクセス対プロパティアクセサの賛否両論の議論は、あなたが先生に読んであげる思いやりの治療の正確一種であるマット・NeubergプログラミングのiOSブック。読んで再読するのはとても役に立ちました。


1
おそらく、(クラスなしで)直接メンバー変数にアクセスしてはならないことを追加する必要があります!そうすることは、OOP(情報隠蔽)の違反です。
2013

2
@HAS正解です。ここでのシナリオは、このクラスの外部からは見えないメンバー変数をプライベートに設定しています。それが、元の投稿者の質問の要点です。他のクラスが設定できreadonlyないようにプロパティを宣言します。
バジルブルク2013

私は読み取り専用プロパティを作成する方法についてこの投稿を見ていた。これは私にはうまくいきません。initで値を割り当てることはできますが、後で割り当てによって_variableを変更することもできます。コンパイラのエラーや警告は発生しません。
netskink 2016年

@BasilBourqueその「持続性」の質問の編集にご協力いただき、誠にありがとうございます。
GhostCat

36

読み取り専用プロパティで機能することがわかった別の方法は、@ synthesizeを使用してバッキングストアを指定することです。例えば

@interface MyClass

@property (readonly) int whatever;

@end

次に実装で

@implementation MyClass

@synthesize whatever = m_whatever;

@end

m_whateverメンバー変数なので、メソッドでを設定できます。


過去数日間に私が気付いたもう1つの興味深いことは、次のようなサブクラスによって書き込み可能な読み取り専用プロパティを作成できることです。

(ヘッダーファイル内)

@interface MyClass
{
    @protected
    int m_propertyBackingStore;
}

@property (readonly) int myProperty;

@end

次に、実装では

@synthesize myProperty = m_propertyBackingStore;

ヘッダーファイルの宣言を使用するので、サブクラスはプロパティの値を更新しながら、読み取り専用のままにすることができます。

データの非表示とカプセル化に関しては、少し残念ですが。


1
あなたが説明した最初の方法は、受け入れられた答えよりも扱いが簡単です。「@synthesize foo1、foo2、foo3;」午前中、そしてすべてが良いです。
sudo 2014

20

iOSドキュメントの既存のクラスのカスタマイズを参照してください。

readonlyプロパティが読み取り専用であることを示します。readonlyを指定する場合、@ implementationで取得メソッドのみが必要です。実装ブロックで@synthesizeを使用する場合、getterメソッドのみが合成されます。さらに、ドット構文を使用して値を割り当てようとすると、コンパイラエラーが発生します。

読み取り専用プロパティには、getterメソッドのみがあります。プロパティのクラス内で直接、またはキー値コーディングを使用して、バッキングivarを設定できます。


8

あなたは他の質問を誤解しています。その質問には、次のように宣言されたクラス拡張があります。

@interface MYShapeEditorDocument ()
@property (readwrite, copy) NSArray *shapesInOrderBackToFront;
@end

これが、クラスの実装内でのみ表示されるセッターを生成するものです。Eikoが言うように、クラス拡張を宣言し、プロパティ宣言をオーバーライドして、クラス内でのみセッターを生成するようコンパイラーに指示する必要があります。


5

最短のソリューションは次のとおりです。

MyClass.h

@interface MyClass {

  int myProperty;

}

@property (readonly) int myProperty;

@end

MyClass.h

@implementation MyClass

@synthesize myProperty;

@end

2

プロパティが読み取り専用として定義されている場合、これは、クラスの内部または他のクラスの外部で使用できるセッターが事実上存在しないことを意味します。(つまり、それが理にかなっている場合にのみ、「ゲッター」を持つことになります。)

そのサウンドから、プライベートとしてマークされた通常の読み取り/書き込みプロパティが必要です。これは、インターフェイスファイルでクラス変数をプライベートとして設定することで実現できます。

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