Sì, ci sono vantaggi nell'uso instancetype
in tutti i casi in cui si applica. Spiegherò più in dettaglio, ma vorrei iniziare con questa affermazione in grassetto: Usa instancetype
ogni 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 id
come valore di ritorno con instancetype
dove appropriato. Questo è in genere il caso di init
metodi 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 id
ritorno da restituire instancetype
, non converte altri metodi. La convenzione Objective-C è di scrivere instancetype
esplicitamente 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 id
in instancetype
. Questo id
è un oggetto generico. Ma se lo rendi un instancetype
compilatore 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 fileHandleWithStandardOutput
come 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 NSFileHandle
fornisce solo un writeData:
lì. Esistono altri esempi, come length
, che restituisce un CGFloat
da UILayoutSupport
ma un NSUInteger
da NSString
.)
Nota : da quando ho scritto questo, le intestazioni macOS sono state modificate per restituire un NSFileHandle
anziché 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 instancetype
da un init
. Ma questo perché il compilatore converte automaticamente id
in instancetype
. Ti affidi a questa stranezza; mentre stai scrivendo che init
restituisce 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 init
e 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 instancetype
come tipo di ritorno di un costruttore, lo otterrai sempre.
Consistenza
Infine, immagina di mettere tutto insieme: vuoi una init
funzione e anche una fabbrica di classe.
Se lo usi id
per 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 instancetype
quando 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 instancetype
molto più frequentemente.