Было бы полезно начать использовать instancetype вместо id? - PullRequest
225 голосов
/ 23 января 2012

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

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

Ответы [ 4 ]

332 голосов
/ 01 февраля 2013

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

190 голосов
/ 23 января 2012

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

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

10 голосов
/ 31 декабря 2014

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

ClassA

@interface ClassA : NSObject

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

@end

Класс B

@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
}
1 голос
/ 23 января 2016

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

**

INSTANCETYPE

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

ID

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...