2

NSString と NSMutableString の両方を、カテゴリを使用したいくつかの便利なメソッドで拡張しました。これらの追加されたメソッドは同じ名前ですが、実装が異なります。たとえば、両方のエンドポイントで空白文字を削除する ruby​​ の「strip」関数を実装しましたが、NSString の場合は新しい文字列を返し、NSMutableString の場合は「deleteCharactersInRange」を使用して既存の文字列を削除して返します (ルビーストリップ!)。

典型的なヘッダーは次のとおりです。

@interface NSString (Extensions)
-(NSString *)strip;
@end

@interface NSMutableString (Extensions)
-(void)strip;
@end

問題は、NSString *s を宣言して [s strip] を実行すると、NSMutableString バージョンを実行しようとし、拡張機能が発生することです。

NSString *s = @"   This is a simple string    ";
NSLog([s strip]);

次のエラーで失敗します:

キャッチされていない例外 'NSInvalidArgumentException' が原因でアプリを終了しています。理由: 'deleteCharactersInRange で不変オブジェクトを変更しようとしています:'

4

3 に答える 3

4
于 2009-08-07T01:04:48.030 に答える
1

あなたが遭遇した問題の鍵は、Objective-C のポリモーフィズムに関する微妙な点にあります。この言語はメソッドのオーバーロードをサポートしていないため、メソッド名は特定のクラス内のメソッドを一意に識別するものと見なされます。オーバーライドされたメソッドは、それがオーバーライドするメソッドと同じセマンティクスを持つという暗黙の (しかし重要な) 仮定があります。

あなたが与えた場合、おそらく2つのメソッドのセマンティクスは同じではありません。つまり、最初のメソッドはレシーバーのコンテンツの「削除された」バージョンで初期化された新しい文字列を返しますが、2 番目のメソッドはレシーバーのコンテンツを直接変更します。これら 2 つの操作は実際には同等ではありません。

Apple が API に名前を付ける方法、特に Foundation を詳しく見てみると、セマンティックなニュアンスを明らかにするのに役立つと思います。たとえば、NSString には、変更されたバージョンのレシーバーを含む新しい文字列を作成するためのメソッドがいくつかあります。

- (NSString *)stringByAppendingFormat:(NSString *)format ...;

名前は名詞であることに注意してください。最初の単語は戻り値を表し、名前の残りの部分は引数を表します。これを、レシーバーに直接追加するための NSMutableString の対応するメソッドと比較します。

- (void)appendFormat:(NSString *)format ...;

対照的に、このメソッドは、説明する戻り値がないため、動詞です。-appendFormat: がレシーバーに作用するのに対し、 -stringByAppendingFormat: は作用せず、代わりに新しい文字列を返すことは、メソッド名だけから明らかです。

(ちなみに、 NSString には、必要なことの少なくとも一部を実行するメソッドが既にあります。引数として-stringByTrimmingCharactersInSet:渡しwhitespaceCharacterSetて、先頭と末尾の空白を削除できます。)

そのため、最初は面倒に思えるかもしれませんが、長い目で見れば、Apple の命名規則をエミュレートしようとする価値があることがわかると思います。他に何もないとしても、特に他の Obj-C 開発者にとって、コードをより自己文書化するのに役立ちます。しかし、Objective-C と Apple のフレームワークのセマンティック上の機微を明確にするのにも役立つと思います。

また、クラス クラスタの内部の詳細が当惑する可能性があることに同意します。特に、それらはほとんど不透明であるためです。ただし、NSString は可変インスタンスと不変インスタンスの両方に NSCFString を使用するクラス クラスターであるという事実は変わりません。したがって、2 番目のカテゴリが別のメソッドを追加すると、最初のカテゴリによって追加されたメソッド-stripが置き換えられます。-strip1 つまたは両方のメソッドの名前を変更すると、この問題が解消されます。

また、同じ機能を提供するメソッドが NSString に既に存在するため、変更可能なメソッドを追加するだけで間違いありません。理想的には、その名前は既存のメソッドと一致するため、次のようになります。

- (void)trimCharactersInSet:(NSCharacterSet *)set
于 2010-03-04T18:35:48.587 に答える
1

例を示すと、この問題が理解しやすくなります。

@interface Foo : NSObject {
}
- (NSString *)method;
@end

@interface Bar : Foo {
}
- (void)method;
@end

void MyFunction(void) {
    Foo *foo = [[[Bar alloc] init] autorelease];
    NSString *string = [foo method];
}

上記のコードでは、"Bar" のインスタンスが割り当てられますが、呼び出し先 (MyFunction のコード) は、型 Foo を介してその Bar オブジェクトへの参照を持っています。呼び出し先が知る限り、foo は "name" を実装して、ストリング。ただし、foo は実際には bar のインスタンスであるため、文字列を返しません。

ほとんどの場合、継承されたメソッドの戻り値の型または引数の型を安全に変更することはできません。あなたがそれを行うことができるいくつかの特別な方法があります。それらは共分散と反分散と呼ばれます。基本的に、継承されたメソッドの戻り値の型をより強い型に変更したり、継承されたメソッドの引数の型をより弱い型に変更したりできます。この背後にある合理的な理由は、すべてのサブクラスがその基本クラスのインターフェイスを満たさなければならないということです。

したがって、「メソッド」の戻り値の型を NSString * から void に変更することは合法ではありませんが、NSString * から NSMutableString * に変更することは合法です。

于 2009-08-07T04:19:08.657 に答える