Ответ 2
Да, есть преимущества при использовании instancetype
во всех случаях, когда это применимо. Я объясню более подробно, но позвольте мне начать с этого жирного утверждения: Используйте instancetype
всякий раз, когда это уместно, что когда класс возвращает экземпляр этого же класса.
Фактически, вот что сейчас говорит Apple по этому вопросу:
В вашем коде замените вхождения id
как возвращаемое значение с instancetype
, где это необходимо. Обычно это относится к методам init
и классу factory. Несмотря на то, что компилятор автоматически преобразует методы, которые начинаются с "alloc", "init" или "new" и имеют возвращаемый тип id
для возврата instancetype
, он не конвертирует другие методы. Objective-C соглашение - писать instancetype
явно для всех методов.
С этой точки зрения, давайте двигаться дальше и объяснять, почему это хорошая идея.
Во-первых, некоторые определения:
@interface Foo:NSObject
- (id)initWithBar:(NSInteger)bar; // initializer
+ (id)fooWithBar:(NSInteger)bar; // class factory
@end
Для класса factory вы должны всегда использовать 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
, но a NSUInteger
из NSString
.)
Примечание. Поскольку я написал это, заголовки macOS были изменены, чтобы вернуть NSFileHandle
вместо id
.
Для инициализаторов это сложнее. Когда вы наберете это:
- (id)initWithBar:(NSInteger)bar
... компилятор будет притворяться, что вы набрали это вместо этого:
- (instancetype)initWithBar:(NSInteger)bar
Это было необходимо для ARC. Это описано в языковых расширениях Clang Связанные типы результатов. Вот почему люди скажут вам, что нет необходимости использовать instancetype
, хотя я утверждаю, что вы должны. Остальная часть этого ответа касается этого.
Есть три преимущества:
- Явно. Ваш код делает то, что он говорит, а не что-то еще.
- Шаблон.. Вы создаете хорошие привычки во времена, которые имеют значение, которые существуют.
- Консистенция.. Вы создали некоторую согласованность с вашим кодом, что делает его более читаемым.
Явное
Верно, что нет технической выгоды для возврата instancetype
из init
. Но это связано с тем, что компилятор автоматически преобразует id
в instancetype
. Вы полагаетесь на эту причуду; в то время как вы пишете, что init
возвращает id
, компилятор интерпретирует его так, как будто он возвращает instancetype
.
Они эквивалентны компилятору:
- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;
Они не эквивалентны вашим глазам. В лучшем случае вы научитесь игнорировать разницу и избегать ее. Это не то, чему вы должны научиться игнорировать.
шаблон
Пока нет разницы с init
и другими методами, есть разница, как только вы определяете класс factory.
Эти два не эквивалентны:
+ (id)fooWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Вам нужна вторая форма. Если вы использовали для ввода instancetype
в качестве возвращаемого типа конструктора, вы будете получать его правильно каждый раз.
Согласованность
Наконец, представьте, если вы соедините все это: вам нужна функция init
, а также класс factory.
Если вы используете id
для init
, вы получите код следующим образом:
- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Но если вы используете instancetype
, вы получите следующее:
- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Это более последовательное и читаемое. Они возвращают то же самое, и теперь это очевидно.
Заключение
Если вы намеренно пишете код для старых компиляторов, вы должны использовать instancetype
, когда это необходимо.
Вы должны смутиться, прежде чем писать сообщение, которое возвращает id
. Спросите себя: это возврат экземпляра этого класса? Если да, то instancetype
.
Конечно, есть случаи, когда вам нужно вернуть id
, но вы, вероятно, будете использовать instancetype
гораздо чаще.
Ответ 4
Вы также можете получить подробную информацию в Назначенный инициализатор
**
INSTANCETYPE
**
Это ключевое слово может использоваться только для типа возврата, которое соответствует типу возвращаемого ресивера. метод init всегда объявляется для возврата типа instancetype.
Например, почему бы не сделать партию возвращаемого типа для партийного экземпляра?
Это вызовет проблему, если класс партии будет когда-либо подклассифицирован. Подкласс наследует все методы от Стороны, включая инициализатор и его тип возврата. Если экземпляр подкласса был отправлен это сообщение инициализатора, это будет возврат? Не указатель на экземпляр партии, а указатель на экземпляр подкласса. Вы можете подумать, что нет проблем, я переопределю инициализатор в подклассе, чтобы изменить тип возврата. Но в Objective-C у вас не может быть двух методов с одним и тем же селектором и разными типами возврата (или аргументами). Указав, что метод инициализации возвращает "экземпляр принимающего объекта", вам никогда не придется беспокоиться о том, что происходит в этой ситуации.
**
ID
**
До того, как instancetype был введен в Objective-C, инициализаторы возвращают id (eye-dee). Этот тип определяется как "указатель на любой объект". (id во многом похож на void * на C.). На момент написания шаблонов класса XCode по-прежнему используется id в качестве возвращаемого типа инициализаторов, добавленных в шаблонный код.
В отличие от instancetype, id может использоваться как больше, чем просто тип возврата. Вы можете объявлять переменные или параметры метода идентификатора типа, когда вы не знаете, какой тип объекта будет указывать на переменную.
Вы можете использовать id при использовании быстрого перечисления для итерации по массиву из нескольких или неизвестных типов объектов. Обратите внимание, что поскольку id undefined как "указатель на любой объект", вы не включаете * при объявлении переменной или объектного параметра этого типа.