Было бы полезно начать использовать instancetype вместо id?

Clang добавляет ключевое слово instancetype что, насколько я вижу, заменяет id в качестве типа возврата в -alloc а также init,

Есть ли польза от использования instancetype вместо id?

5 ответов

Решение

Там определенно есть выгода. Когда вы используете 'id', вы практически не проверяете тип. С помощью instancetype компилятор и IDE знают, какой тип объектов возвращается, и могут лучше проверять ваш код и лучше выполнять автозаполнение.

Используйте его только тогда, когда это имеет смысл (т. Е. Метод, возвращающий экземпляр этого класса); Идентификатор все еще полезен.

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

Есть три преимущества:

  1. Явный. Ваш код делает то, что говорит, а не что-то еще.
  2. Шаблон. Вы создаете хорошие привычки в те времена, когда это имеет значение, которые существуют.
  3. Согласованность. Вы установили некоторую согласованность с вашим кодом, что делает его более читабельным.

Явный

Это правда, что нет никакой технической выгоды для возвращения 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 гораздо чаще

Приведенных выше ответов более чем достаточно, чтобы объяснить этот вопрос. Я просто хотел бы добавить пример для читателей, чтобы понять его с точки зрения кодирования.

ClassA

@interface ClassA : NSObject

- (id)methodA;
- (instancetype)methodB;

@end

Класс б

@interface ClassB : NSObject

- (id)methodX;

@end

TestViewController.m

#import "ClassA.h"
#import "ClassB.h"

- (void)viewDidLoad {

    [[[[ClassA alloc] init] methodA] methodX]; //This will NOT generate a compiler warning or error because the return type for methodA is id. Eventually this will generate exception at runtime

    [[[[ClassA alloc] init] methodB] methodX]; //This will generate a compiler error saying "No visible @interface ClassA declares selector methodX" because the methodB returns instanceType i.e. the type of the receiver
}

Вы также можете получить подробную информацию в назначенном инициализаторе

**

INSTANCETYPE

** Это ключевое слово может использоваться только для типа возвращаемого значения, которое совпадает с типом возвращаемого значения получателя. Метод init всегда объявляется как возвращающий тип экземпляра. Почему бы не сделать, например, возвращаемый тип Party для party party? Это вызвало бы проблему, если бы класс партии был когда-либо подклассом. Подкласс будет наследовать все методы от Party, включая инициализатор и его тип возврата. Если экземпляру подкласса было отправлено это сообщение инициализатора, что будет возвращено? Не указатель на экземпляр Party, а указатель на экземпляр подкласса. Вы можете подумать, что это не проблема, я переопределю инициализатор в подклассе, чтобы изменить тип возвращаемого значения. Но в Objective-C нельзя использовать два метода с одним и тем же селектором и разными типами возвращаемых данных (или аргументами). Указав, что метод инициализации возвращает "экземпляр получающего объекта", вам никогда не придется беспокоиться о том, что происходит в этой ситуации. **

Я БЫ

** Перед тем, как тип экземпляра был введен в Objective-C, инициализаторы возвращают id (eye-dee). Этот тип определяется как "указатель на любой объект". (id очень похож на void * в C.) На момент написания, шаблоны классов XCode по-прежнему используют id в качестве возвращаемого типа инициализаторов, добавленных в шаблонный код. В отличие от instancetype, id может использоваться как нечто большее, чем просто возвращаемый тип. Вы можете объявить переменные или параметры метода типа id, если вы не уверены, на какой тип объекта будет указывать переменная. Вы можете использовать id при использовании быстрого перечисления для перебора массива объектов нескольких или неизвестных типов. Обратите внимание, что, поскольку id не определен как "указатель на любой объект", вы не включаете * при объявлении переменной или параметра объекта этого типа.

Особый тип instancetype indicates that the return type from the method will be the same class as the type of object it is initializing (that is, the receiver of the message). This is an aid for the compiler so that it can check your program and flag potential type mismatches—it determines the class of the returned object based on context; that is, if you’re sending the message to a newly alloc’ed object, the compiler will infer that the value returned from that init method (whose return type has been declared as type instancetype) will be a Fraction object. In the past the return type from an initialization method was declared as type id. This new type makes more sense when you consider subclassing, as the inherited initialization methods cannot explicitly define the type of object they will return.

Инициализация объектов, Стивен Г. Кочан, Программирование на Objective-C, 6-е издание

Другие вопросы по тегам