Ja, die Verwendung bietet Vorteile instancetypein allen Fällen, in denen sie gilt. Ich werde es genauer erklären, aber lassen Sie mich mit dieser kühnen Aussage beginnen: Verwenden Sie sie, instancetypewann immer es angebracht ist, dh wann immer eine Klasse eine Instanz derselben Klasse zurückgibt.
In der Tat ist hier, was Apple jetzt zu diesem Thema sagt:
Ersetzen Sie in Ihrem Code Vorkommen idals Rückgabewert instancetypegegebenenfalls durch. Dies ist normalerweise bei initMethoden und Klassenfactory-Methoden der Fall . Obwohl der Compiler automatisch Methoden konvertiert, die mit "alloc", "init" oder "new" beginnen und einen Rückgabetyp von " idreturn" haben instancetype, konvertiert er keine anderen Methoden. Die Objective-C-Konvention besteht darin, instancetypeexplizit für alle Methoden zu schreiben .
Lassen Sie uns weitermachen und erklären, warum dies eine gute Idee ist.
Zunächst einige Definitionen:
@interface Foo:NSObject
- (id)initWithBar:(NSInteger)bar; // initializer
+ (id)fooWithBar:(NSInteger)bar; // class factory
@end
Für eine Klassenfabrik sollten Sie immer verwenden instancetype. Der Compiler konvertiert nicht automatisch idin instancetype. Das idist ein generisches Objekt. Wenn Sie es jedoch zu einem instancetypeObjekt machen, weiß der Compiler, welchen Objekttyp die Methode zurückgibt.
Dies ist kein akademisches Problem. Beispielsweise [[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]wird unter Mac OS X ( nur ) ein Fehler generiert. Mehrere Methoden mit dem Namen 'writeData:' wurden mit nicht übereinstimmendem Ergebnis, Parametertyp oder Attributen gefunden . Der Grund ist, dass sowohl NSFileHandle als auch NSURLHandle a bereitstellen writeData:. Da der Compiler [NSFileHandle fileHandleWithStandardOutput]ein zurückgibt id, ist er nicht sicher, welche Klasse writeData:aufgerufen wird.
Sie müssen dies umgehen, indem Sie entweder Folgendes verwenden:
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];
oder:
NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];
Die bessere Lösung besteht natürlich darin, fileHandleWithStandardOutputeine Rückgabe zu deklarieren instancetype. Dann ist die Besetzung oder Aufgabe nicht notwendig.
(Beachten Sie, dass dieses Beispiel unter iOS keinen Fehler erzeugt, da nur NSFileHandleein writeData:dort angegeben wird. Es gibt andere Beispiele, z. B. length, die ein CGFloatvon UILayoutSupportaber ein NSUIntegervon zurückgeben NSString.)
Hinweis : Seit ich dies geschrieben habe, wurden die macOS-Header so geändert, dass sie a NSFileHandleanstelle von a zurückgeben id.
Für Initialisierer ist es komplizierter. Wenn Sie dies eingeben:
- (id)initWithBar:(NSInteger)bar
… Der Compiler wird so tun, als hätten Sie dies stattdessen eingegeben:
- (instancetype)initWithBar:(NSInteger)bar
Dies war für ARC notwendig. Dies wird unter Clang Language Extensions Related-Ergebnistypen beschrieben . Dies ist der Grund, warum die Leute Ihnen sagen werden, dass es nicht notwendig ist, es zu verwenden instancetype, obwohl ich der Meinung bin, dass Sie es sollten. Der Rest dieser Antwort befasst sich damit.
Es gibt drei Vorteile:
- Explizit. Ihr Code tut, was er sagt, und nicht etwas anderes.
- Muster. Sie bauen gute Gewohnheiten für Zeiten auf, in denen es wichtig ist, welche existieren.
- Konsistenz. Sie haben eine gewisse Konsistenz für Ihren Code hergestellt, wodurch er besser lesbar ist.
Explizit
Es ist wahr, dass es keinen technischen Vorteil gibt, instancetypevon einem zurückzukehren init. Aber das ist , weil der Compiler automatisch die konvertiert idzu instancetype. Sie verlassen sich auf diese Eigenart; Während Sie schreiben, dass das ein initzurückgibt id, interpretiert der Compiler es so, als ob es ein zurückgibt instancetype.
Diese entsprechen dem Compiler:
- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;
Diese entsprechen nicht Ihren Augen. Bestenfalls werden Sie lernen, den Unterschied zu ignorieren und ihn zu überfliegen. Dies sollten Sie nicht ignorieren lernen.
Muster
Während es keinen Unterschied zu initund anderen Methoden gibt, gibt es einen Unterschied, sobald Sie eine Klassenfactory definieren.
Diese beiden sind nicht gleichwertig:
+ (id)fooWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Sie möchten das zweite Formular. Wenn Sie es gewohnt sind, instancetypeals Rückgabetyp eines Konstruktors zu tippen, werden Sie es jedes Mal richtig machen.
Konsistenz
Stellen Sie sich zum Schluss vor, Sie setzen alles zusammen: Sie wollen eine initFunktion und auch eine Klassenfabrik.
Wenn Sie idfür verwenden init, erhalten Sie Code wie folgt:
- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Aber wenn Sie verwenden instancetype, erhalten Sie Folgendes:
- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Es ist konsistenter und lesbarer. Sie geben dasselbe zurück, und das ist jetzt offensichtlich.
Fazit
Sofern Sie nicht absichtlich Code für alte Compiler schreiben, sollten Sie diesen instancetypegegebenenfalls verwenden.
Sie sollten zögern, bevor Sie eine Nachricht schreiben, die zurückkehrt id. Fragen Sie sich: Gibt dies eine Instanz dieser Klasse zurück? Wenn ja, ist es ein instancetype.
Es gibt sicherlich Fälle, in denen Sie zurückkehren müssen id, aber Sie werden wahrscheinlich instancetypeviel häufiger verwenden.