Objective-C ランタイムは、宣言されたプロパティのリストを Class オブジェクトのメタデータとして保持します。メタデータには、プロパティ名、タイプ、および属性が含まれます。ランタイム ライブラリには、これらの情報を取得するための関数もいくつか用意されています。これは、宣言されたプロパティがアクセサー メソッド (getter/setter) のペア以上のものであることを意味します。私の最初の質問は、なぜ私たち (またはランタイム) がメタデータを必要とするのですか?
よく知られているように、宣言されたプロパティはサブクラスでオーバーライドできません (readwrite と readonly を除く)。しかし、私はそれが必要であることを保証するシナリオを持っています:
@interface MyClass : MySuperClass <NSCopying, NSMutableCopying>
@property (nonatomic, copy, readonly) NSString *string;
- (id)initWithString:(NSString *)aString;
@end
@interface MyMutableClass : MyClass
@property (nonatomic, strong, readwrite) NSMutableString *string;
- (id)initWithString:(NSString *)aString;
@end
もちろん、コンパイラは上記のコードを通過させません。私の解決策は、宣言されたプロパティをアクセサー メソッドのペアに置き換えることです (読み取り専用の場合はゲッターのみ)。
@interface MyClass : MySuperClass <NSCopying, NSMutableCopying> {
NSString *_string;
}
- (id)initWithString:(NSString *)aString;
- (NSString *)string;
@end
@implementation MyClass
- (id)initWithString:(NSString *)aString {
self = [super init...];
if (self) {
_string = [aString copy];
}
return self;
}
- (NSString *)string {
return _string;
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
return [[MyMutableClass alloc] initWithString:self.string];
}
@end
@interface MyMutableClass : MyClass
- (id)initWithString:(NSString *)aString;
- (NSMutableString *)string;
- (void)setString:(NSMutableString *)aMutableString;
- (void)didMutateString;
@end
@implementation MyMutableClass
- (id)initWithString:(NSString *)aString {
self = [super init...];
if (self) {
_string = [aString mutableCopy];
}
return self;
}
- (NSMutableString *)string {
return (NSMutableString *)_string;
}
- (void)setString:(NSMutableString *)aMutableString {
_string = aMutableString;
// Inform other parts that `string` has been changed (as a whole).
// ...
}
- (void)didMutateString {
// The content of `string` has been changed through the interface of
// NSMutableString, beneath the accessor method.
// ...
}
- (id)copyWithZone:(NSZone *)zone {
return [[MyClass alloc] initWithString:self.string];
}
@end
プロパティstring
は段階的に変更され、頻繁に変更される可能性があるため、変更可能である必要があります。同じセレクターを持つメソッドは同じ戻り値とパラメーターの型を共有する必要があるという制約を知っています。しかし、上記の解決策は意味的にも技術的にも適切だと思います。セマンティックな側面では、可変オブジェクトは不変オブジェクトです。技術的な面では、コンパイラはすべてのオブジェクトをidとしてエンコードします。私の2番目の質問は、上記の解決策は理にかなっていますか? それとも単に奇妙ですか?
次のように、ハイブリッド アプローチを採用することもできます。
@interface MyClass : MySuperClass <NSCopying, NSMutableCopying> {
NSString *_string;
}
@property (nonatomic, copy, readonly) NSString *string;
- (id)initWithString:(NSString *)aString;
@end
@interface MyMutableClass: MyClass
- (id)initWithString:(NSString *)aString;
- (NSMutableString *)string;
- (void)setString:(NSMutableString *)aMutableString;
- (void)didMutateString;
@end
ただし、 のようなドット構文を使用してプロパティにアクセスするmyMutableObject.string
と、コンパイラは、アクセサ メソッドの戻り値の型が宣言されたプロパティの型と一致しないことを警告します。メッセージフォームとしてご利用いただいても構いません[myMutableObject string]
。これは、宣言されたプロパティがアクセサー メソッドのペア以上のものであるという別の側面を示唆しています。つまり、ここでは望ましくありませんが、より静的な型チェックです。3 番目の質問は、サブクラスでオーバーライドする場合、宣言されたプロパティの代わりに getter/setter ペアを使用するのが一般的ですか?