Oui, il y a des avantages à utiliser instancetype
dans 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 instancetype
chaque 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 id
comme valeur de retour par le instancetype
cas échéant. C'est généralement le cas pour les init
mé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 id
retour à renvoyer instancetype
, il ne convertit pas les autres méthodes. La convention Objective-C consiste à écrire instancetype
explicitement 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 id
en instancetype
. C'est id
un objet générique. Mais si vous en faites un, instancetype
le 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 fileHandleWithStandardOutput
comme 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 NSFileHandle
fournit qu'un writeData:
. D'autres exemples existent, tels que length
, qui renvoie un CGFloat
de UILayoutSupport
mais un NSUInteger
de NSString
.)
Remarque : depuis que j'ai écrit ceci, les en-têtes macOS ont été modifiés pour renvoyer un NSFileHandle
au 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 instancetype
d'un init
. Mais c'est parce que le compilateur convertit automatiquement le fichier id
en instancetype
. Vous comptez sur cette bizarrerie; pendant que vous écrivez que le init
renvoie 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 init
autres 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 instancetype
comme type de retour d'un constructeur, vous aurez toujours raison.
Cohérence
Enfin, imaginez si vous mettez tout cela ensemble: vous voulez une init
fonction et aussi une fabrique de classe.
Si vous utilisez id
pour 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 instancetype
lorsque 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 instancetype
beaucoup plus fréquemment.