Sì, ci sono vantaggi nell'uso instancetypein tutti i casi in cui si applica. Spiegherò più in dettaglio, ma vorrei iniziare con questa affermazione in grassetto: Usa instancetypeogni volta che è appropriato, ovvero ogni volta che una classe restituisce un'istanza di quella stessa classe.
In effetti, ecco cosa dice ora Apple sull'argomento:
Nel tuo codice, sostituisci le occorrenze di idcome valore di ritorno con instancetypedove appropriato. Questo è in genere il caso di initmetodi e metodi di classe di fabbrica. Anche se il compilatore converte automaticamente i metodi che iniziano con "alloc", "init" o "new" e hanno un tipo di idritorno da restituire instancetype, non converte altri metodi. La convenzione Objective-C è di scrivere instancetypeesplicitamente per tutti i metodi.
Detto questo, andiamo avanti e spieghiamo perché è una buona idea.
Innanzitutto, alcune definizioni:
@interface Foo:NSObject
- (id)initWithBar:(NSInteger)bar; // initializer
+ (id)fooWithBar:(NSInteger)bar; // class factory
@end
Per una fabbrica di classe, dovresti sempre usare instancetype. Il compilatore non si converte automaticamente idin instancetype. Questo idè un oggetto generico. Ma se lo rendi un instancetypecompilatore sa quale tipo di oggetto restituisce il metodo.
Questo non è un problema accademico. Ad esempio, [[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]genererà un errore su Mac OS X ( solo ) Più metodi denominati "writeData:" trovati con risultati, tipo di parametro o attributi non corrispondenti . Il motivo è che sia NSFileHandle che NSURLHandle forniscono a writeData:. Poiché [NSFileHandle fileHandleWithStandardOutput]restituisce an id, il compilatore non è certo su quale classe writeData:venga chiamata.
È necessario aggirare questo problema utilizzando:
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];
o:
NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];
Naturalmente, la soluzione migliore è dichiarare fileHandleWithStandardOutputcome restituire un instancetype. Quindi il cast o il compito non sono necessari.
(Si noti che su iOS, questo esempio non produrrà un errore in quanto NSFileHandlefornisce solo un writeData:lì. Esistono altri esempi, come length, che restituisce un CGFloatda UILayoutSupportma un NSUIntegerda NSString.)
Nota : da quando ho scritto questo, le intestazioni macOS sono state modificate per restituire un NSFileHandleanziché un id.
Per gli inizializzatori, è più complicato. Quando si digita questo:
- (id)initWithBar:(NSInteger)bar
... il compilatore fingerà invece di averlo digitato:
- (instancetype)initWithBar:(NSInteger)bar
Questo era necessario per ARC. Questo è descritto nei tipi di risultati relativi alle estensioni di lingua di Clang . Questo è il motivo per cui le persone ti diranno che non è necessario usare instancetype, anche se io sostengo che dovresti. Il resto di questa risposta si occupa di questo.
Ci sono tre vantaggi:
- Esplicito. Il tuo codice sta facendo quello che dice, piuttosto che qualcos'altro.
- Modello. Stai costruendo buone abitudini per le volte che contano, che esistono.
- Consistenza. Hai stabilito una certa coerenza con il tuo codice, il che lo rende più leggibile.
Esplicito
È vero che non c'è alcun vantaggio tecnico nel tornare instancetypeda un init. Ma questo perché il compilatore converte automaticamente idin instancetype. Ti affidi a questa stranezza; mentre stai scrivendo che initrestituisce an id, il compilatore lo sta interpretando come se restituisse an instancetype.
Questi sono equivalenti al compilatore:
- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;
Questi non sono equivalenti ai tuoi occhi. Nella migliore delle ipotesi, imparerai a ignorare la differenza e passarci sopra. Questo non è qualcosa che dovresti imparare a ignorare.
Modello
Mentre non c'è alcuna differenza con inite altri metodi, non v'è una differenza non appena si definisce una class factory.
Questi due non sono equivalenti:
+ (id)fooWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Vuoi il secondo modulo. Se sei abituato a digitare instancetypecome tipo di ritorno di un costruttore, lo otterrai sempre.
Consistenza
Infine, immagina di mettere tutto insieme: vuoi una initfunzione e anche una fabbrica di classe.
Se lo usi idper init, finisci con un codice come questo:
- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Ma se usi instancetype, ottieni questo:
- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
È più coerente e più leggibile. Restituiscono la stessa cosa, e ora è ovvio.
Conclusione
A meno che tu non stia scrivendo intenzionalmente codice per vecchi compilatori, dovresti usarlo instancetypequando appropriato.
Dovresti esitare prima di scrivere un messaggio che ritorna id. Chiediti: questo restituisce un'istanza di questa classe? Se è così, è un instancetype.
Ci sono certamente casi in cui è necessario tornare id, ma probabilmente lo utilizzerai instancetypemolto più frequentemente.