6

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 ペアを使用するのが一般的ですか?

4

1 に答える 1

3

これに対する私の見方は少し異なります。Objective-C クラスの場合、@interfaceそのクラスと通信するすべてのクラスで使用する API を宣言します。NSString*コピー プロパティを強力なプロパティに置き換えることNSMutableString*で、予期しない副作用が発生しやすい状況を作り出しています。

特に、NSString*コピー プロパティは、不変オブジェクトを返すことが期待されます。これは、NSMutableString*オブジェクトがそうでない多くの状況 (辞書のキー、 の要素名NSXMLElement) で安全に使用できます。そのため、この方法でこれらを置き換えたくありません。

基になる が必要な場合NSMutableStringは、次のことをお勧めします。

  • NSMutableString*文字列プロパティに加えてプロパティを追加し、名前を付けます-mutableString
  • を作成して保存-setString:するメソッドをオーバーライドするNSMutableString
  • メソッドをオーバーライドして-string、可変文字列の不変コピーを返す
  • 内部 ivar を NSMutableString に置き換えることができるかどうかを慎重に評価してください。元のクラスにアクセスできず、クラス内の文字列の可変性について仮定が行われているかどうかが不明な場合、これは問題になる可能性があります。

これを行うと、クラスの既存のユーザーを混乱させることなく現在のインターフェイスを維持しながら、新しいパラダイムに対応するように動作を拡張できます。

可変オブジェクトと不変オブジェクトの間で変更を行う場合、オブジェクトの API コントラクトを破らないように注意する必要があります。

于 2012-05-29T11:50:26.640 に答える