2009-08-07 7 views
2

Ich habe beide NSString und NSMutableString mit einigen Komfort-Methoden mit Kategorien erweitert. Diese hinzugefügten Methoden haben denselben Namen, aber unterschiedliche Implementierungen. Zum Beispiel habe ich die Ruby - Funktion "strip" implementiert, die Leerzeichen an den Endpunkten für beide entfernt, aber für NSString gibt sie eine neue Zeichenkette zurück, und für NSMutableString verwendet sie "deleteCharactersInRange", um die vorhandene Zeichenkette zu entfernen und zurückzugeben (wie die Rubinstreifen!).Kategorien für NSMutableString und NSString, die verbindliche Verwirrung verursachen?

Hier ist die typische Kopf:

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

und

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

Das Problem ist, dass, wenn ich NSString * s und führen [s Streifen] erklären, er versucht, die NSMutableString Version und Raises laufen eine Erweiterung.

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

nicht mit:

beenden app aufgrund nicht abgefangene Ausnahme 'NSInvalidArgumentException', Grund: 'Versuch unveränderliches Objekt mit deleteCharactersInRange zu mutieren:'

+1

Wenn der Wert von S nicht festgelegt ist, sollten Sie nie etwas wie NSLog ([s strip]) tun. Wenn s den Wert @ "% @" hätte, würde es abstürzen. Es ist in der Regel nicht sinnvoll, darüber nachzudenken, ob Sie die Domäne s festgelegt haben, stattdessen sollten Sie sich stattdessen angewöhnen, dies zu tun: NSLog (@ "% @", [s strip]); –

Antwort

3

Sie‘ Durch ein Implementierungsdetail wurde gebissen: Einige NSString-Objekte sind Instanzen einer Unterklasse von NSMutableString mit nur einem privaten Flag Kontrollieren, ob das Objekt veränderbar ist oder nicht.

Hier ist ein Test App:

#import <Foundation/Foundation.h> 

int main(int argc, char **argv) { 
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 

    NSString *str = [NSString stringWithUTF8String:"Test string"]; 
    NSLog(@"%@ is a kind of NSMutableString? %@", [str class], [str isKindOfClass:[NSMutableString class]] ? @"YES" : @"NO"); 

    [pool drain]; 
    return EXIT_SUCCESS; 
} 

Wenn Sie dies auf Leopard (mindestens) kompilieren und ausführen, werden Sie diese Ausgabe erhalten:

NSCFString is a kind of NSMutableString? YES 

Wie gesagt, hat das Objekt eine private Flagge, die kontrolliert, ob es veränderbar ist oder nicht. Da ich NSString und nicht NSMutableString durchlaufen habe, ist dieses Objekt nicht veränderbar. Wenn Sie versuchen, es zu mutieren, wie folgt aus:

NSMutableString *mstr = str; 
[mstr appendString:@" is mutable!"]; 

Sie erhalten (1) eine wohlverdiente Warnung (die man mit einem gegossenen Schweigen könnte, aber das wäre eine schlechte Idee) und (2) die gleiche Ausnahme, die Sie in Ihrer eigenen Anwendung erhalten haben.

Die Lösung, die ich vorschlagen, ist Ihr mutierenden Streifen in einem @try Block zu wickeln, und rufen Sie bis zu Ihrer NSString Implementierung (return [super strip]) im @catch Block.

Auch würde ich nicht empfehlen, die Methode verschiedene Rückgabetypen geben. Ich würde die mutierende eine Rückkehr self, wie retain und autorelease tun. Dann können Sie dies immer tun:

NSString *unstripped = …; 
NSString *stripped = [unstripped strip]; 

ohne sich Gedanken darüber, ob unstripped ist ein veränderliches Zeichenfolge oder nicht. In der Tat ist dieses Beispiel ein guter Fall, dass Sie die mutierende strip vollständig entfernen oder das Kopieren strip zu stringByStripping oder etwas umbenennen sollten (in Analogie zu replaceOccurrencesOfString:… und stringByReplacingOccurrencesOfString:…).

+0

Dieses Problem wurde vor langer Zeit (Panther Days) auf http://www.cocoabuilder.com/archive/message/cocoa/2004/7/8/111294 diskutiert. Wenn Sie Leopard (zumindest) sagen, implizieren Sie das das ist in einer zukünftigen Version von Mac OS X behoben? – 0xced

+0

Dies hat nichts damit zu tun, wie NSString wirklich implementiert ist. Indem Sie zwei verschiedene Methoden wie oben beschrieben deklarieren, spielen Sie mit Feuer, unabhängig davon, wie die Klasse intern implementiert wird. –

+0

0xced: Könnte sein. Ich kann es nicht wissen. Außerdem habe ich Tiger oder andere Versionen nicht versucht. Ich weiß nur, dass es bei Leopard so funktioniert. –

1

Ein Beispiel wird das Problem mit dieser leichter zu verstehen:

@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]; 
} 

In dem obigen Code, eine Instanz von „Bar“ zugewiesen werden, aber die Angerufene (der Code in MyFunction) eine Referenz zu diesem Bar-Objekt durch den Typ Foo, so weit der Angerufene weiß, implementiert foo "name", um eine Zeichenkette zurückzugeben. Da foo jedoch tatsächlich eine Instanz von bar ist, gibt es keine Zeichenfolge zurück.

In den meisten Fällen können Sie den Rückgabetyp oder die Argumenttypen einer vererbten Methode nicht sicher ändern. Es gibt einige spezielle Möglichkeiten, wie Sie es tun können. Sie heißen covariance and contravariance. Grundsätzlich können Sie den Rückgabetyp einer geerbten Methode in einen stärkeren Typ ändern, und Sie können die Argumenttypen einer geerbten Methode in einen schwächeren Typ ändern. Der Grund dafür ist, dass jede Unterklasse die Schnittstelle ihrer Basisklasse erfüllen muss.

Obwohl es nicht legal ist, den Rückgabetyp von "method" von NSString * auf void zu ändern, wäre es legal, ihn von NSString * zu NSMutableString * zu ändern.

+0

Whoa, ich habe nicht einmal bemerkt, dass er ihnen verschiedene Rückgabetypen gegeben hat. Das ist natürlich ein guter Grund, das nicht zu tun. –

1

Der Schlüssel zu dem Problem, auf das Sie gestoßen sind, beruht auf einem subtilen Punkt über Polymorphie in Objective-C. Da die Sprache das Überladen von Methoden nicht unterstützt, wird angenommen, dass ein Methodenname eine Methode innerhalb einer bestimmten Klasse eindeutig identifiziert. Es gibt eine implizite (aber wichtige) Annahme, dass eine überschriebene Methode dieselbe Semantik wie die überschriebene Methode hat.

In dem Fall, den Sie angegeben haben, ist wohl die Semantik von zwei Methoden nicht das gleiche; h., die erste Methode gibt eine neue Zeichenfolge zurück, die mit einer "abgestreiften" Version des Inhalts des Empfängers initialisiert wurde, während die zweite Methode den Inhalt des Empfängers direkt modifiziert. Diese beiden Operationen sind nicht gleichwertig.

Ich denke, wenn man sich genauer anschaut, wie Apple seine APIs nennt, besonders in Foundation, kann das wirklich helfen, einige semantische Nuancen aufzuklären. Zum Beispiel in NSString gibt es mehrere Methoden für eine neue Zeichenfolge der Schaffung einer modifizierten Version des Empfängers, wie

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

Hinweis enthält, dass der Name ein Substantiv ist, wo das erste Wort des Rückgabewert beschreibt, und den Rest des Namens beschreibt das Argument. Nun vergleichen Sie diese mit dem entsprechenden Verfahren in NSMutableString für direkt an den Empfänger anhängt:

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

dagegen diese Methode ist ein Verb, weil es kein Rückgabewert zu beschreiben. So ist aus dem Methodennamen allein klar, dass -appendFormat: auf den Empfänger einwirkt, während -stringByAppendingFormat: nicht und stattdessen eine neue Zeichenkette zurückgibt.

(By the way, gibt es bereits ein Verfahren in NSString, die zumindest einen Teil tut, was Sie wollen. -stringByTrimmingCharactersInSet: Sie whitespaceCharacterSet als Argument übergeben können Leerzeichen trimmen Vorder- und Hinter.)

So, während es kann Anfangs scheint es nervig zu sein, ich denke, Sie werden es auf lange Sicht wirklich sehen, wenn Sie versuchen, Apples Namenskonventionen nachzuahmen. Wenn nichts anderes hilft, wird Ihr Code besonders für andere Obj-C-Entwickler selbstdokumentierter. Aber ich denke, es wird auch helfen, einige semantische Feinheiten von Objective-C und Apples Frameworks zu klären.

Auch stimme ich zu, dass die internen Details der Klassenhaufen beunruhigend sein können, zumal sie für uns meist undurchsichtig sind.Es bleibt jedoch die Tatsache, dass NSString ein Klassencluster ist, der NSCFString für veränderbare und unveränderbare Instanzen verwendet. Wenn Ihre zweite Kategorie also eine weitere -strip-Methode hinzufügt, ersetzt sie die -strip-Methode, die von der ersten Kategorie hinzugefügt wird. Wenn Sie den Namen einer oder beider Methoden ändern, wird dieses Problem behoben.

Und da in NSString bereits eine Methode vorhanden ist, die dieselbe Funktionalität bietet, könnten Sie einfach die Methode "mutable" hinzufügen. Idealerweise würde sein Name der bestehenden Methode entsprechen, also wäre es:

- (void)trimCharactersInSet:(NSCharacterSet *)set 
Verwandte Themen