Було б вигідно почати використовувати instancetype замість id?


226

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

Чи є користь від використання instancetypeзамість id?


10
Ні .. не для alloc та init, оскільки вони вже працюють так. Суть типуtype полягає в тому, що ви можете надати власні методи alloc / init, як behavoir.
hooleyhoop

@hooleyhoop вони не працюють так. Вони повертають ідентифікатор. id - об’єкт Obj-C. Це все. Компілятор нічого не знає про повернене значення, окрім цього.
griotspeak

5
вони працюють так, як перевірка контракту та типу вже відбувається для init, лише для зберігачів це вам потрібно. nshipster.com/instancetype
kocodude

Речі просунулися зовсім небагато, так. Пов'язані типи результатів є відносно новими, а інші зміни означають, що це буде набагато яснішим питанням незабаром.
griotspeak

1
Я просто хотів би прокоментувати, що зараз на iOS 8 багато методів, які використовували для повернення id, були замінені instancetype, навіть initз NSObject. Якщо ви хочете зробити код сумісним зі швидким, який вам доведеться використовуватиinstancetype
Hola Soy Edu Feliz Navidad

Відповіді:


192

Однозначно є користь. Використовуючи "id", ви фактично не перевіряєте тип. За допомогою typetype компілятор і IDE знають, який тип речі повертається, і можуть краще перевірити ваш код і краще заповнити його.

Використовуйте його лише там, де це має сенс звичайно (тобто метод, який повертає екземпляр цього класу); id все ще корисний.


8
alloc, initі т.д., автоматично сприяє instancetypeкомпілятор. Це не означає, що користі немає; є, але це не це.
Стівен Фішер

10
це зручно для зручності конструкторів
Catfish_Man

5
Він був представлений разом із (і щоб підтримати підтримку) ARC, з виходом Lion у 2011 році. Одне, що ускладнює повсюдне прийняття в Какао, - це те, що всі методи-кандидати повинні бути перевірені, щоб побачити, чи роблять вони [self alloc], а не [NameOfClass alloc], тому що це було б дуже заплутано, щоб зробити [SomeSubClass удобствоConstructor], причому + comfortConstructor оголошено повернути екземпляр, і щоб він не повертав екземпляр SomeSubClass.
Catfish_Man

2
'id' перетворюється в typetype лише тоді, коли компілятор може зробити висновок про сімейство методів. Це, звичайно, не робить це для зручності конструкторів або інших методів повернення ідентифікаторів.
Catfish_Man

4
Зауважте, що в iOS 7 багато методів у Фонді були перетворені на typetype. Я особисто бачив цей улов щонайменше у 3 випадках неправильного раніше існуючого коду.
Catfish_Man

336

Так, є користь для використання 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]повертає an 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замість an id.

Для ініціалізаторів це складніше. Коли ви вводите це:

- (id)initWithBar:(NSInteger)bar

… Компілятор зробить вигляд, що ви набрали це замість:

- (instancetype)initWithBar:(NSInteger)bar

Це було необхідно для ARC. Це описано в Clang Мова Extensions типів суміжних результатів . Ось чому люди скажуть вам, що використовувати їх не потрібно instancetype, хоча я заперечую, що ви повинні. Решта цієї відповіді стосується цього.

Є три переваги:

  1. Явне. Ваш код робить те, що говорить, а не щось інше.
  2. Візерунок. Ви формуєте гарні звички на час, який має значення, які існують.
  3. Послідовність. Ви встановили певну узгодженість свого коду, що робить його більш читабельним.

Явне

Це правда, що повернення з пункту немає технічної користі . Але це тому, що компілятор автоматично перетворює на . Ви покладаєтесь на цю вигадку; поки ви пишете, що повертає an , компілятор інтерпретує його так, ніби він повертає .instancetypeinitidinstancetypeinitidinstancetype

Вони еквівалентні компілятору:

- (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набагато частіше.


4
Минув час, коли я запитав це, і я давно зайняв таку позицію, як та, яку ви висловили тут. Я відпустив це сюди, тому що думаю, що, на жаль, це буде вважатися питанням стилю для init, і інформація, представлена ​​в попередніх відповідях, відповіла досить чітко.
griotspeak

6
Щоб було зрозуміло: я вважаю, що відповідь Catfish_Man правильна лише у першому реченні . Відповідь hooleyhoop правильна, за винятком першого речення . Це пекло дилеми; Я хотів надати щось, що, з часом, буде вважатися кориснішим і правильнішим за будь-який. (При всій повазі до цих двох; зрештою, це зараз набагато очевидніше, ніж це було тоді, коли були написані їх відповіді.)
Стівен Фішер

1
З часом я би сподівався. Я не можу подумати про якусь причину цього не робити, якщо Apple не вважає, що важливо підтримувати сумісність джерела з (наприклад) присвоєнням нового NSArray NSString без виступу.
Стівен Фішер

4
instancetypeпроти idсправді не стильове рішення. Останні зміни навколо instancetypeнасправді дають зрозуміти, що нам слід користуватися instancetypeв таких місцях, як -initми маємо на увазі "екземпляр мого класу"
griotspeak

2
Ви сказали, що є причини використання ідентифікатора, чи можете ви детальніше розглянути? Єдине, що я можу придумати, - це стислість та зворотна сумісність; враховуючи, що обидва коштують за висловлення свого наміру, я думаю, що це справді погані аргументи.
Стівен Фішер

10

Наведених вище відповідей більш ніж достатньо для пояснення цього питання. Я просто хотів би додати приклад для читачів, щоб зрозуміти це з точки зору кодування.

КласА

@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
}

це призведе до помилки компілятора, лише якщо ви не використовуєте #import "ClassB.h". інакше компілятор припустить, що ви знаєте, що робите. наприклад, наступні компілюються, але виходять з ладу під час виконання: id myNumber = @ (1); id iAssumedThatWasAnArray = myNumber [0]; id iAssumedThatWasADictionary = [myNumber objectForKey: @ "ключ"];
OlDor

1

Ви також можете ознайомитись з інформацією про цей призначений ініціалізатор

**

ІНСТАНЦЕТИП

** Це ключове слово можна використовувати лише для типу повернення, яке воно відповідає типу повернення приймача. метод init завжди оголошується для повернення typetype. Чому б, наприклад, не зробити Party Party типу для екземпляра партії? Це спричинило б проблему, якщо партійний клас був коли-небудь підкласовим. Підклас успадкував би всі методи від Party, включаючи ініціалізатор та його тип повернення. Якщо екземпляру підкласу було надіслано це повідомлення ініціалізатора, це було б поверненням? Не вказівник на екземпляр учасника, а вказівник на екземпляр підкласу. Ви можете подумати, що це не проблема, я заміню ініціалізатор в підкласі, щоб змінити тип повернення. Але в Objective-C ви не можете мати два способи з тим самим селектором і різними типами повернення (або аргументами). Вказавши, що метод ініціалізації повертається "

Посвідчення особи

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.