Было бы полезно начать использовать instancetype вместо id?
Clang добавляет ключевое слово instancetype это, насколько я вижу, заменяет id как возвращаемый тип в -alloc и init.
есть ли польза от использования instancetype вместо id?
4 ответов:
там определенно есть преимущество. Когда вы используете 'id', вы получаете по существу никакой проверки типа вообще. С instancetype компилятор и IDE знают, какой тип вещи возвращается, и могут лучше проверить ваш код и автозаполнение лучше.
использовать его только там где это имеет смысл, конечно (т. е. метод, который возвращает экземпляр этого класса); ID-это еще полезно.
Да, есть преимущества в использовании
instancetypeво всех случаях, когда оно применяется. Я объясню более подробно, но позвольте мне начать с этого смелого утверждения: Useinstancetypeвсякий раз, когда это уместно, то есть всякий раз, когда класс возвращает экземпляр этого же класса.на самом деле, вот что Apple сейчас говорит по этому поводу:
в коде замените вхождения
idкак возвращаемое значение сinstancetypeгде это уместно. Это обычно имеет место дляinitметоды класса и методы. Несмотря на то, что компилятор автоматически преобразует методы, которые начинаются с "alloc", "init" или "new" и имеют тип возвратаidвернутьсяinstancetype, он не преобразует другие методы. цель-c конвенция состоит в том, чтобы написатьinstancetypeявно для всех методов.
- выделено мной. Источник:Принятие Современной Цели-C
с этим в сторону, давайте двигаться и объясните, почему это хорошая идея.
во-первых, некоторые определения:
@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, который возвращает aCGFloatСUILayoutSupportно aNSUIntegerСNSString.)Примечание: с тех пор как я написал это, заголовки macOS были изменены, чтобы вернуть
NSFileHandleвместо anid.для инициализаторов, это сложнее. При вводе этого:
- (id)initWithBar:(NSInteger)bar...компилятор будет делать вид, что вы набрали это вместо:
- (instancetype)initWithBar:(NSInteger)barэто было необходимо для дуги. Это описано в разделе Расширения языка Clang типы, связанные с результатом. Вот почему люди скажут вам, что это не обязательно использовать
instancetype, хотя я утверждаю, что вы должны. Остальная часть этого ответа касается этого.есть три преимущества:
- явный. ваш код делает то, что он говорит, а не что-то другое.
- узор. вы строите хорошие привычки Для раз это имеет значение, которые существуют.
- последовательности. вы установили некоторую согласованность с вашим кодом, что делает его более читаемым.
явно
это правда, что нет технические польза для возвращения
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