Да, использование 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]
возвращает id
, компилятор не уверен, к какому классу writeData:
вызывается.
Вам нужно обойти это, используя либо:
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];
или
NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];
Конечно, лучшее решение - объявить fileHandleWithStandardOutput
возвращающим instancetype
. Тогда приведение или назначение не требуется.
(Обратите внимание, что в iOS этот пример не выдаст ошибку, так как только NSFileHandle
предоставляет writeData:
. Существуют и другие примеры, например length
, который возвращает CGFloat
из UILayoutSupport
, но NSUInteger
от NSString
.)
Примечание : с тех пор, как я написал это, заголовки macOS были изменены, чтобы возвращать NSFileHandle
вместо id
.
Для инициализаторов это сложнее. Когда вы набираете это:
- (id)initWithBar:(NSInteger)bar
… компилятор сделает вид, что вы набрали это:
- (instancetype)initWithBar:(NSInteger)bar
Это было необходимо для ARC. Это описано в Clang Language Extensions Связанные типы результатов . Вот почему люди скажут вам, что нет необходимости использовать instancetype
, хотя я утверждаю, что вы должны. Остальная часть этого ответа имеет дело с этим.
Есть три преимущества:
- Явный. Ваш код делает то, что говорит, а не что-то еще.
- Шаблон. Вы создаете хорошие привычки для тех случаев, когда это имеет значение, которые существуют.
- Согласованность. Вы установили некоторую согласованность своего кода, что делает его более читабельным.
Явные
Это правда, что нет технической выгоды от возврата instancetype
из init
. Но это потому, что компилятор автоматически преобразует id
в instancetype
. Вы полагаетесь на эту причуду; пока вы пишете, что init
возвращает 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
гораздо чаще.