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



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



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

469   4  

4 ответов:

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

использовать его только там где это имеет смысл, конечно (т. е. метод, который возвращает экземпляр этого класса); ID-это еще полезно.

Да, есть преимущества в использовании instancetype во всех случаях, когда оно применяется. Я объясню более подробно, но позвольте мне начать с этого смелого утверждения: Use instancetype всякий раз, когда это уместно, то есть всякий раз, когда класс возвращает экземпляр этого же класса.

на самом деле, вот что Apple сейчас говорит по этому поводу:

в коде замените вхождения id как возвращаемое значение с instancetype где это уместно. Это обычно имеет место для init методы класса и методы. Несмотря на то, что компилятор автоматически преобразует методы, которые начинаются с "alloc", "init" или "new" и имеют тип возврата id вернуться instancetype, он не преобразует другие методы. цель-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, который возвращает a CGFloat С UILayoutSupport но a NSUInteger С NSString.)

Примечание: с тех пор как я написал это, заголовки macOS были изменены, чтобы вернуть NSFileHandle вместо an id.

для инициализаторов, это сложнее. При вводе этого:

- (id)initWithBar:(NSInteger)bar

...компилятор будет делать вид, что вы набрали это вместо:

- (instancetype)initWithBar:(NSInteger)bar

это было необходимо для дуги. Это описано в разделе Расширения языка Clang типы, связанные с результатом. Вот почему люди скажут вам, что это не обязательно использовать instancetype, хотя я утверждаю, что вы должны. Остальная часть этого ответа касается этого.

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

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

явно

это правда, что нет технические польза для возвращения instancetype С init. Но это потому, что компилятор автоматически преобразует id до instancetype. Вы полагаетесь на эту причуду; в то время как вы пишете, что init возвращает id, компилятор интерпретирует его так, как будто он возвращает instancetype.

это эквивалентно чтобы компилятор:

- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;

они не эквивалентны вашим глазам. В лучшем случае, вы научитесь игнорировать разницу и пролистывать ее. это не то, что вы должны научиться игнорировать.

Pattern

в то время как нет никакой разницы с 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

Класс B

@interface ClassB : NSObject

- (id)methodX;

@end

TestViewController.м

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

ID

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

Comments

    Ничего не найдено.