Ja, die Verwendung bietet Vorteile instancetype
in 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, instancetype
wann 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 id
als Rückgabewert instancetype
gegebenenfalls durch. Dies ist normalerweise bei init
Methoden und Klassenfactory-Methoden der Fall . Obwohl der Compiler automatisch Methoden konvertiert, die mit "alloc", "init" oder "new" beginnen und einen Rückgabetyp von " id
return" haben instancetype
, konvertiert er keine anderen Methoden. Die Objective-C-Konvention besteht darin, instancetype
explizit 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 id
in instancetype
. Das id
ist ein generisches Objekt. Wenn Sie es jedoch zu einem instancetype
Objekt 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, fileHandleWithStandardOutput
eine 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 NSFileHandle
ein writeData:
dort angegeben wird. Es gibt andere Beispiele, z. B. length
, die ein CGFloat
von UILayoutSupport
aber ein NSUInteger
von zurückgeben NSString
.)
Hinweis : Seit ich dies geschrieben habe, wurden die macOS-Header so geändert, dass sie a NSFileHandle
anstelle 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, instancetype
von einem zurückzukehren init
. Aber das ist , weil der Compiler automatisch die konvertiert id
zu instancetype
. Sie verlassen sich auf diese Eigenart; Während Sie schreiben, dass das ein init
zurü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 init
und 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, instancetype
als 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 init
Funktion und auch eine Klassenfabrik.
Wenn Sie id
fü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 instancetype
gegebenenfalls 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 instancetype
viel häufiger verwenden.