Sarebbe utile iniziare a usare instancetype invece di id?

Clang aggiunge un instancetype parole chiave che, per quanto posso vedere, sostituisce l’ id come un tipo di ritorno in -alloc e init .

C’è un vantaggio nell’usare instancetype invece di id ?

C’è sicuramente un vantaggio. Quando si utilizza ‘id’, in pratica non si ottiene alcun tipo di controllo. Con instancetype, il compilatore e l’IDE sanno che tipo di cosa viene restituita, e possono controllare meglio il tuo codice e completare l’autocompletamento.

Usalo solo dove ha senso, ovviamente (cioè un metodo che restituisce un’istanza di quella class); id è ancora utile.

Sì, ci sono benefici nell’utilizzo di instancetype in tutti i casi in cui si applica. Spiegherò in maggior dettaglio, ma lasciatemi iniziare con questa affermazione audace: usa instancetype ogni volta che è appropriato, ovvero quando una class restituisce un’istanza della stessa class.

In effetti, ecco cosa Apple dice ora sull’argomento:

Nel codice, sostituisci occorrenze di id come valore di ritorno con instancetype dove appropriato. Questo è in genere il caso dei metodi init e dei metodi factory class. Anche se il compilatore converte automaticamente i metodi che iniziano con “alloc”, “init” o “new” e hanno un tipo restituito di id per restituire instancetype , non converte altri metodi. La convenzione Objective-C è di scrivere instancetype esplicitamente per tutti i metodi.

  • Enfasi mia. Fonte: adozione di Modern Objective-C

Con quello fuori mano, 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 class, dovresti sempre usare instancetype . Il compilatore non converte automaticamente id per instancetype . Questo id è un object generico. Ma se lo fai un instancetype il compilatore sa quale tipo di object restituisce il metodo.

Questo non è un problema accademico. Ad esempio, [[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData] genererà un errore su Mac OS X ( solo ) Metodi multipli denominati ‘writeData:’ trovati con risultati non corrispondenti, tipo di parametro o attributi . Il motivo è che sia NSFileHandle che NSURLHandle forniscono dati di writeData: Poiché [NSFileHandle fileHandleWithStandardOutput] restituisce un id , il compilatore non è sicuro della class writeData: viene richiamata.

È necessario aggirare questo problema, utilizzando uno dei due:

 [(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 solo NSFileHandle fornisce un writeData: lì esistono altri esempi, come la length , che restituisce un CGFloat da UILayoutSupport ma un NSUInteger da NSString .)

Nota : poiché ho scritto questo, le intestazioni macOS sono state modificate per restituire un NSFileHandle invece di un id .

Per gli inizializzatori, è più complicato. Quando digiti questo:

 - (id)initWithBar:(NSInteger)bar 

… il compilatore farà finta di averlo scritto invece:

 - (instancetype)initWithBar:(NSInteger)bar 

Questo era necessario per ARC. Questo è descritto nei tipi di risultati relativi alle estensioni del linguaggio di Clang. Questo è il motivo per cui la gente ti dirà che non è necessario usare instancetype , anche se io sostengo che dovresti. Il resto di questa risposta si occupa di questo.

Ci sono tre vantaggi:

  1. Esplicito. Il tuo codice sta facendo quello che dice, piuttosto che qualcos’altro.
  2. Modello. Stai costruendo buone abitudini per le volte che conta, che esistono.
  3. Consistenza. Hai stabilito una certa coerenza con il tuo codice, che lo rende più leggibile.

Esplicito

È vero che non c’è alcun vantaggio tecnico nel restituire instancetype da un init . Ma questo perché il compilatore converte automaticamente l’ id per instancetype . Stai facendo affidamento su questa stranezza; mentre stai scrivendo che init restituisce un id , il compilatore lo sta interpretando come se restituisse un instancetype .

Questi sono equivalenti al compilatore:

 - (id)initWithBar:(NSInteger)bar; - (instancetype)initWithBar:(NSInteger)bar; 

Questi non sono equivalenti ai tuoi occhi. Nel migliore dei casi, imparerai a ignorare la differenza e a sfiorarti. Questo non è qualcosa che dovresti imparare a ignorare.

Modello

Sebbene non ci siano differenze con init e altri metodi, c’è una differenza non appena si definisce una factory di class.

Questi due non sono equivalenti:

 + (id)fooWithBar:(NSInteger)bar; + (instancetype)fooWithBar:(NSInteger)bar; 

Vuoi la seconda forma. Se sei abituato a digitare instancetype come tipo di ritorno di un costruttore, avrai sempre ragione.

Consistenza

Infine, immagina di metterlo insieme: vuoi una funzione di init e anche una factory di class.

Se usi id per init , init 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 non si stia scrivendo intenzionalmente codice per vecchi compilatori, è necessario utilizzare instancetype quando appropriato.

Dovresti esitare prima di scrivere un messaggio che restituisce l’ id . Chiediti: è questo il ritorno di un’istanza di questa class? Se è così, è un instancetype .

Ci sono certamente casi in cui è necessario restituire id , ma probabilmente userete instancetype molto più frequentemente.

Le risposte sopra sono più che sufficienti per spiegare questa domanda. Vorrei solo aggiungere un esempio per i lettori per capirlo in termini di codifica.

Classe A

 @interface ClassA : NSObject - (id)methodA; - (instancetype)methodB; @end 

Classe B

 @interface ClassB : NSObject - (id)methodX; @end 

TestViewController.m

 #import "ClassA.h" #import "ClassB.h" - (void)viewDidLoad { [[[[ClassA alloc] init] methodA] methodX]; //This will NOT generate a compiler warning or error because the return type for methodA is id. Eventually this will generate exception at runtime [[[[ClassA alloc] init] methodB] methodX]; //This will generate a compiler error saying "No visible @interface ClassA declares selector methodX" because the methodB returns instanceType ie the type of the receiver } 

È anche ansible ottenere dettagli su The Designated Initializer

**

instanceType

** Questa parola chiave può essere utilizzata solo per il tipo di reso, che corrisponde al tipo di destinatario del reso. metodo init sempre dichiarato di restituire instancetype. Perché non rendere il tipo di ritorno Party per istanza di partito, ad esempio? Ciò causerebbe un problema se la class del Partito fosse mai stata sottoclass. La sottoclass erediterebbe tutti i metodi da Party, incluso l’inizializzatore e il suo tipo di ritorno. Se un’istanza della sottoclass è stata inviata questo messaggio di inizializzazione, ciò sarebbe restituito? Non un puntatore a un’istanza di Party, ma un puntatore a un’istanza di sottoclass. Potresti pensare che sia Nessun problema, sostituirò l’inizializzatore nella sottoclass per cambiare il tipo di ritorno. Ma in Objective-C, non puoi avere due metodi con lo stesso selettore e diversi tipi di ritorno (o argomenti). Specificando che un metodo di inizializzazione restituisce “un’istanza dell’object ricevente”, non dovresti mai preoccuparti di ciò che accade in questa situazione. **

ID

** Prima che l’instancetype sia stato introdotto in Objective-C, gli inizializzatori restituiscono id (eye-dee). Questo tipo è definito come “un puntatore a qualsiasi object”. (id è molto simile a void * in C.) Al momento della stesura, i modelli di class XCode usano ancora id come tipo di ritorno degli inizializzatori aggiunti nel codice boilerplate. A differenza di instancetype, id può essere utilizzato come qualcosa di più di un semplice tipo di ritorno. È ansible dichiarare variabili o parametri di metodo dell’id del tipo quando non si è sicuri del tipo di object a cui la variabile finirà per puntare. È ansible utilizzare id quando si utilizza l’enumerazione rapida per scorrere su una matrice di tipi di oggetti multipli o sconosciuti. Si noti che poiché id non è definito come “un puntatore a qualsiasi object”, non si include * quando si dichiara una variabile o un parametro object di questo tipo.