Oui, il y a des avantages à utiliser instancetypedans tous les cas où cela s'applique. J'expliquerai plus en détail, mais permettez-moi de commencer par cette déclaration en gras: utilisez instancetypechaque fois que cela est approprié, c'est-à-dire chaque fois qu'une classe renvoie une instance de cette même classe.
En fait, voici ce qu'Apple dit maintenant sur le sujet:
Dans votre code, remplacez les occurrences de idcomme valeur de retour par le instancetypecas échéant. C'est généralement le cas pour les initméthodes et les méthodes de fabrique de classe. Même si le compilateur convertit automatiquement les méthodes qui commencent par «alloc», «init» ou «new» et ont un type de idretour à renvoyer instancetype, il ne convertit pas les autres méthodes. La convention Objective-C consiste à écrire instancetypeexplicitement pour toutes les méthodes.
Avec cela à l'écart, passons à autre chose et expliquons pourquoi c'est une bonne idée.
Tout d'abord, quelques définitions:
@interface Foo:NSObject
- (id)initWithBar:(NSInteger)bar; // initializer
+ (id)fooWithBar:(NSInteger)bar; // class factory
@end
Pour une usine de classe, vous devez toujours utiliser instancetype. Le compilateur ne se convertit pas automatiquement iden instancetype. C'est idun objet générique. Mais si vous en faites un, instancetypele compilateur sait quel type d'objet la méthode retourne.
Ce n'est pas un problème académique. Par exemple, [[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]générera une erreur sur Mac OS X ( uniquement ) Plusieurs méthodes nommées «writeData:» trouvées avec un résultat, un type de paramètre ou des attributs incompatibles . La raison en est que NSFileHandle et NSURLHandle fournissent a writeData:. Depuis [NSFileHandle fileHandleWithStandardOutput]retourne un id, le compilateur n'est pas certain de quelle classe writeData:est appelée.
Vous devez contourner cela en utilisant soit:
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];
ou:
NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];
Bien sûr, la meilleure solution est de déclarer fileHandleWithStandardOutputcomme retournant un instancetype. Ensuite, le casting ou l'affectation n'est pas nécessaire.
(Notez que sur iOS, cet exemple ne produira pas d'erreur car il n'en NSFileHandlefournit qu'un writeData:. D'autres exemples existent, tels que length, qui renvoie un CGFloatde UILayoutSupportmais un NSUIntegerde NSString.)
Remarque : depuis que j'ai écrit ceci, les en-têtes macOS ont été modifiés pour renvoyer un NSFileHandleau lieu d'un id.
Pour les initialiseurs, c'est plus compliqué. Lorsque vous tapez ceci:
- (id)initWithBar:(NSInteger)bar
… Le compilateur prétendra que vous avez tapé ceci à la place:
- (instancetype)initWithBar:(NSInteger)bar
C'était nécessaire pour l'ARC. Ceci est décrit dans les types de résultats Clang Language Extensions Related . C'est pourquoi les gens vous diront qu'il n'est pas nécessaire d'utiliser instancetype, bien que je soutienne que vous devriez. Le reste de cette réponse traite de cela.
Il y a trois avantages:
- Explicite. Votre code fait ce qu'il dit, plutôt que quelque chose d'autre.
- Modèle. Vous construisez de bonnes habitudes pour les moments où cela compte, qui existent.
- Cohérence. Vous avez établi une certaine cohérence à votre code, ce qui le rend plus lisible.
Explicite
C'est vrai qu'il n'y a aucun avantage technique à revenir instancetyped'un init. Mais c'est parce que le compilateur convertit automatiquement le fichier iden instancetype. Vous comptez sur cette bizarrerie; pendant que vous écrivez que le initrenvoie un id, le compilateur l'interprète comme s'il renvoyait un instancetype.
Ceux-ci sont équivalents au compilateur:
- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;
Ce ne sont pas équivalents à vos yeux. Au mieux, vous apprendrez à ignorer la différence et à la survoler. Ce n'est pas quelque chose que vous devez apprendre à ignorer.
Modèle
Bien qu'il n'y ait pas de différence avec les initautres méthodes, il y a une différence dès que vous définissez une fabrique de classe.
Ces deux ne sont pas équivalents:
+ (id)fooWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Vous voulez le deuxième formulaire. Si vous avez l'habitude de taper instancetypecomme type de retour d'un constructeur, vous aurez toujours raison.
Cohérence
Enfin, imaginez si vous mettez tout cela ensemble: vous voulez une initfonction et aussi une fabrique de classe.
Si vous utilisez idpour init, vous vous retrouvez avec un code comme celui-ci:
- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Mais si vous utilisez instancetype, vous obtenez ceci:
- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
C'est plus cohérent et plus lisible. Ils retournent la même chose, et maintenant c'est évident.
Conclusion
À moins que vous n'écriviez intentionnellement du code pour d'anciens compilateurs, vous devez l'utiliser instancetypelorsque cela est approprié.
Vous devriez hésiter avant d'écrire un message qui revient id. Posez-vous la question: cela renvoie-t-il une instance de cette classe? Si oui, c'est un instancetype.
Il y a certainement des cas où vous devez revenir id, mais vous en utiliserez probablement instancetypebeaucoup plus fréquemment.