Да, есть преимущества использования 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
гораздо чаще.