Так, є користь для використання 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
, хоча я заперечую, що ви повинні. Решта цієї відповіді стосується цього.
Є три переваги:
- Явне. Ваш код робить те, що говорить, а не щось інше.
- Візерунок. Ви формуєте гарні звички на час, який має значення, які існують.
- Послідовність. Ви встановили певну узгодженість свого коду, що робить його більш читабельним.
Явне
Це правда, що повернення з пункту немає технічної користі . Але це тому, що компілятор автоматично перетворює на . Ви покладаєтесь на цю вигадку; поки ви пишете, що повертає an , компілятор інтерпретує його так, ніби він повертає .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
набагато частіше.