Sarebbe utile iniziare a utilizzare instancetype anziché id?


226

Clang aggiunge una parola chiave instancetypeche, per quanto posso vedere, sostituisce idcome tipo di ritorno in -alloce init.

C'è un vantaggio nell'usare instancetypeinvece di id?


10
No .. non per alloc e init, poiché funzionano già in questo modo. Il punto di instancetype è che puoi assegnare metodi personalizzati alloc / init come behavoir.
hooleyhoop,

@hooleyhoop non funzionano così. Restituiscono id. id è "un oggetto Obj-C". Questo è tutto. Il compilatore non conosce altro sul valore restituito.
Griotspeak

5
funzionano in questo modo, il contratto e la verifica del tipo sono già in corso per init, solo per i produttori ne hai bisogno. nshipster.com/instancetype
kocodude

Le cose sono avanzate un bel po ', sì. I tipi di risultati correlati sono relativamente nuovi e altre modifiche significano che questo sarà presto un problema molto più chiaro.
griotspeak

1
Vorrei solo commentare che ora su iOS 8 idsono stati sostituiti molti metodi usati per tornare instancetype, anche initda NSObject. Se vuoi rendere il tuo codice compatibile con swift devi usareinstancetype
Hola Soy Edu Feliz Navidad

Risposte:


192

C'è sicuramente un vantaggio. Quando usi "id", praticamente non ottieni alcun controllo del tipo. Con instancetype, il compilatore e l'IDE sanno quale tipo di cosa viene restituita e possono controllare meglio il codice e completare automaticamente.

Usalo solo dove ha senso ovviamente (cioè un metodo che sta restituendo un'istanza di quella classe); id è ancora utile.


8
alloc, initecc. vengono promossi automaticamente instancetypedal compilatore. Questo non vuol dire che non ci sono benefici; c'è, ma non è questo.
Steven Fisher,

10
è utile soprattutto per i costruttori di articoli di convenienza
Catfish_Man,

5
È stato introdotto con (e per aiutare a supportare) ARC, con l'uscita di Lion nel 2011. Una cosa che complica l'adozione diffusa in Cocoa è che tutti i metodi candidati devono essere controllati per vedere se fanno [auto-allocazione] piuttosto che [NameOfClass alloc], perché sarebbe molto confuso fare [SomeSubClass convenienceConstructor], con + convenienceConstructor ha dichiarato di restituire instancetype e non ha restituito un'istanza di SomeSubClass.
Catfish_Man

2
'id' viene convertito in instancetype solo quando il compilatore può inferire la famiglia di metodi. Certamente non lo fa per costruttori di convenienza o altri metodi di restituzione dell'ID.
Catfish_Man

4
Si noti che in iOS 7, molti metodi in Foundation sono stati convertiti in instancetype. Personalmente ho visto questa cattura almeno 3 casi di codice preesistente errato.
Catfish_Man

336

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:

  1. Esplicito. Il tuo codice sta facendo quello che dice, piuttosto che qualcos'altro.
  2. Modello. Stai costruendo buone abitudini per le volte che contano, che esistono.
  3. 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.


4
È passato un po 'di tempo da quando l'ho chiesto e da tempo ho assunto una posizione come quella che hai presentato qui. L'ho lasciato andare qui perché penso che, sfortunatamente, questo sarà considerato un problema di stile per init e le informazioni presentate nelle risposte precedenti hanno risposto alla domanda abbastanza chiaramente.
Griotspeak

6
Per essere chiari: credo che la risposta di Catfish_Man sia corretta solo nella sua prima frase . La risposta di hooleyhoop è corretta, tranne per la sua prima frase . È un dilemma infernale; Volevo fornire qualcosa che, nel tempo, sarebbe stato più utile e più corretto di entrambi. (Con tutto il rispetto per quei due; dopotutto, questo è molto più ovvio ora di quanto non fosse quando erano state scritte le loro risposte.)
Steven Fisher,

1
Nel tempo lo spero. Non riesco a pensare a nessun motivo per non farlo, a meno che Apple non ritenga importante mantenere la compatibilità dei sorgenti con (ad esempio) l'assegnazione di un nuovo NSArray a un NSString senza cast.
Steven Fisher,

4
instancetypevs idnon è davvero una decisione di stile. I recenti cambiamenti in giro instancetypechiariscono davvero che dovremmo usare instancetypein luoghi come -init"un'istanza della mia classe"
griotspeak

2
Hai detto che c'erano dei motivi per usare l'id, puoi approfondire? Le uniche due che mi vengono in mente sono la brevità e la retrocompatibilità; considerando che entrambi sono a scapito di esprimere il tuo intento, penso che questi siano argomenti davvero scadenti.
Steven Fisher,

10

Le risposte sopra sono più che sufficienti per spiegare questa domanda. Vorrei solo aggiungere un esempio affinché i lettori lo capiscano 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 i.e. the type of the receiver
}

questo genererà un errore del compilatore solo se non si utilizza #import "ClassB.h". altrimenti, il compilatore supporrà che tu sappia cosa stai facendo. ad esempio, vengono compilati i seguenti elementi, ma si arresta in modo anomalo in fase di esecuzione: id myNumber = @ (1); id iAssumedThatWasAnArray = myNumber [0]; id iAssumedThatWasADictionary = [myNumber objectForKey: @ "key"];
OlDor,

1

È inoltre possibile ottenere dettagli in The Designated Initializer

**

instanceType

** Questa parola chiave può essere utilizzata solo per il tipo restituito, che corrisponde al tipo restituito del ricevitore. il metodo init ha sempre dichiarato di restituire instancetype. Perché non rendere ad esempio il tipo restituito Party per l'istanza party? Ciò causerebbe un problema se la classe del Partito fosse mai stata sottoclassata. La sottoclasse erediterebbe tutti i metodi da Party, incluso l'inizializzatore e il suo tipo di ritorno. Se a un'istanza della sottoclasse fosse inviato questo messaggio di inizializzazione, sarebbe restituito? Non un puntatore a un'istanza di Party, ma un puntatore a un'istanza di sottoclasse. Potresti pensare che non sia un problema, sovrascriverò l'inizializzatore nella sottoclasse per cambiare il tipo di ritorno. Ma in Objective-C, non puoi avere due metodi con lo stesso selettore e tipi di ritorno (o argomenti) diversi. Specificando che un metodo di inizializzazione restituisce "

ID

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.