Да, есть преимущества использования instancetypeво всех случаях, когда это применимо. Я объясню более подробно, но позвольте мне начать с этого жирного выражения: используйте, instancetypeкогда это уместно, то есть всякий раз, когда класс возвращает экземпляр того же класса.
Фактически, вот что Apple сейчас говорит по этому вопросу:
В своем коде замените вхождения idв качестве возвращаемого значения на instancetypeсоответствующие. Обычно это относится к initметодам и методам фабрики классов. Хотя компилятор автоматически преобразует методы, которые начинаются с «alloc», «init» или «new» и имеют возвращаемый тип idдля возврата instancetype, он не преобразует другие методы. Соглашение Objective C состоит в том, чтобы писать instancetypeявно для всех методов.
После этого давайте продолжим и объясним, почему это хорошая идея.
Сначала несколько определений:
@interface Foo:NSObject
- (id)initWithBar:(NSInteger)bar; // initializer
+ (id)fooWithBar:(NSInteger)bar; // class factory
@end
Для фабрики классов вы всегда должны использовать instancetype. Компилятор не конвертируется автоматически idв instancetype. Это idобщий объект. Но если вы сделаете это, instancetypeкомпилятор знает, какой тип объекта возвращает метод.
Это не академическая проблема. Например, [[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]сгенерирует ошибку в Mac OS X ( только ). Несколько методов с именем 'writeData:' найдены с несоответствующим результатом, типом параметра или атрибутами . Причина в том, что и NSFileHandle, и NSURLHandle предоставляют writeData:. Так как [NSFileHandle fileHandleWithStandardOutput]возвращает an id, компилятор не уверен, какой класс writeData:вызывается.
Вам нужно обойти это, используя либо:
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];
или:
NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];
Конечно, лучшим решением будет объявить fileHandleWithStandardOutputвозвращение instancetype. Тогда приведение или назначение не является необходимым.
(Обратите внимание, что в iOS этот пример не выдаст ошибку, поскольку только NSFileHandleпредоставляет writeData:там. Существуют и другие примеры, такие как length, который возвращает a CGFloatиз, UILayoutSupportно a NSUIntegerиз NSString.)
Примечание : так как я написал это, заголовки macOS были изменены, чтобы возвращать NSFileHandleвместо вместо id.
Для инициализаторов это сложнее. Когда вы печатаете это:
- (id)initWithBar:(NSInteger)bar
... компилятор сделает вид, что вы набрали это:
- (instancetype)initWithBar:(NSInteger)bar
Это было необходимо для ARC. Это описано в типах результатов Clang Language Extensions . Вот почему люди скажут вам, что нет необходимости использовать instancetype, хотя я утверждаю, что вы должны. Остальная часть этого ответа имеет дело с этим.
Есть три преимущества:
- Явный. Ваш код делает то, что говорит, а не что-то еще.
- Шаблон. Вы создаете хорошие привычки в те времена, когда это имеет значение, которые существуют.
- Согласованность. Вы установили некоторую согласованность с вашим кодом, что делает его более читабельным.
Явный
Это правда, что нет никакой технической выгоды для возвращения instancetypeиз init. Но это потому, что компилятор автоматически преобразует idв instancetype. Вы полагаетесь на эту причуду; в то время как вы пишете, что initвозвращает an id, компилятор интерпретирует его так, как будто он возвращает instancetype.
Это эквивалентно компилятору:
- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;
Это не эквивалентно вашим глазам. В лучшем случае вы научитесь игнорировать разницу и просматривать ее. Это не то, что вы должны научиться игнорировать.
Шаблон
Хотя нет никакой разницы с initи другими методами, то есть разница , как только вы определяете класс фабрики.
Эти два не эквивалентны:
+ (id)fooWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Вы хотите вторую форму. Если вы привыкли печатать instancetypeв качестве возвращаемого типа конструктора, вы каждый раз будете делать это правильно.
консистенция
Наконец, представьте, если вы соберете все это вместе: вам нужна initфункция, а также фабрика классов.
Если вы используете idдля init, вы получите код, подобный этому:
- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Но если вы используете instancetype, вы получите это:
- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Это более последовательно и более читабельно. Они возвращают то же самое, и теперь это очевидно.
Вывод
Если вы намеренно не пишете код для старых компиляторов, вы должны использовать его в instancetypeслучае необходимости.
Вы должны сомневаться, прежде чем писать сообщение, которое возвращается id. Спросите себя: это возвращает экземпляр этого класса? Если это так, это instancetype.
Конечно, есть случаи, когда вам нужно вернуться id, но вы, вероятно, будете использовать их instancetypeгораздо чаще.