Cocoa Developers Club

Открытый клуб iOS и OS X разработчиков
10 Июнь 2015

instancetype в Objective-C

Written by CocoaDevelopersClub
Картинка профиля CocoaDevelopersClub
Разработка для iOS, Разработка для OS X dev, instancetype, ios, objective-c, osx

В версию 5.0 Xcode (LLVM 5/Clang 5) была добавлена поддержка нового ключевого слова для Objective-C — «instancetype».

Для чего оно нужно?

В соответствии с принятыми в Cocoa соглашениями наименования методов, Objective-C методы с названиями начинающимися с «init», «alloc», «copy», а также методы с названиями «new», «autorelease», «retain», «self» должны возвращать экземпляр класса которому они принадлежат. Такие методы возвращают связанный тип. Послав сообщение одному из этих методов, в ответ мы получим экземпляр класса, которому принадлежит метод.

Рассмотрим небольшой пример:

// Test Class 
@interface TestClass: NSObject
+(id) instance;
@end

@implementation TestClass
+ (id) instance {
    return [[self alloc] init];
}
@end

// Other Test Class
@interface OtherTestClass: TestClass
-(void) doSomething;
@end

@implementation OtherTestClass
-(void) doSomething {
    // ...
}
@end

В примере мы создали класс «TestClass», который реализует метод «instance», и унаследованный от него класс «OtherTestClass», реализующий метод «doSomething».

Теперь пошлем сообщение «doSomething» экземпляру «TestClass»:

[[[TestClass alloc] init] doSomething]; // Ошибка: No visible @interface for 'TestClass' declares the selector 'doSomething'

[[TestClass instance] doSomething]; // Ошибки нет!

При выполнении первой инструкции компилятор (тут и далее по тексту имеется ввиду и компилятор, и статический анализатор) ругнется на неизвестный селектор.

Это происходит потому, что компилятор осуществляет проверку на соответствие возвращаемого типа, базируясь на соглашении наименования методов. Вызов «[TestClass alloc]» возвращает новый экземпляр класса «TestClass», так как «alloc» неявно возвращает связанный тип. Точно также «[[TestClass alloc] init]» возвращает экземпляр класса «TestClass». Зная, что результатом вызова «[[TestClass alloc] init]» является экземпляр класса «TestClass», компилятор проверяет наличие селектора «doSomething».

Выполняя вторую инструкцию, компилятор не ругнется! Как указано в сигнатуре, метод «instance», также как «alloc» и «init», возвращает тип «id». Но в данном случаем проверка возвращаемого типа компилятором не производится, так как название метода не попадает под соглашение наименования и его возвращаемый тип интерпритируется как «id», т.е. возвращаемый тип соответствует указанному в сигнатуре типу «id». В такой ситуации приложение аварийно завершится.

Для решения этой проблемы можно использовать явное приведение типов:

// Все OK. Как и положено компилятор генерирует ошибку: 
// No visible @interface for 'TestClass' declares the selector 'doSomething'
[(TestClass *)[TestClass instance] doSomething];

Теперь компилятор знает тип и проверяет существование селектора.

Также можно в объявлении метода явно указать возвращаемый тип как «TestClass *»:

+(TestClass *) instance;

Компилятор знает возвращаемый тип и выполняет проверку существования селектора. Но тут у нас возникает проблема с наследованием:

// Test Class 
@interface TestClass: NSObject
+(TestClass *) instance;
@end

@implementation TestClass
+ (TestClass *) instance {
    return [[self alloc] init];
}
@end

// Other Test Class
@interface OtherTestClass: TestClass
-(void) doSomething;
@end

@implementation OtherTestClass
-(void) doSomething {
    // ...
}
@end

//...
[[OtherTestClass instance] doSomething]; //Ошибка: No visible @interface for 'TestClass' declares the selector 'doSomething'

Так как «instance» возвращает экземпляр класса «TestClass», у которого нет селектора «doSomething», то компилятор генерирует ошибку. Для решения данной проблемы нам необходимо в классе «OtherTestClass» переопределить метод «instance»:

// Other Test Class
@interface OtherTestClass: TestClass
-(void) doSomething;
+(OtherTestClass *) instance;
@end

@implementation OtherTestClass
-(void) doSomething {
    // ...
}
+ (OtherTestClass *) instance {
    return [[self alloc] init];
}
@end

Рассмотрим еще один пример. Допустим в классе «OtherTestClass» мы перегрузим метод «instance», которые возвращает тип «id»:

// Test Class 
@interface TestClass: NSObject
+(id) instance;
@end

@implementation TestClass
+ (id) instance {
    return [[self alloc] init];
}
@end

// Other Test Class
@interface OtherTestClass: TestClass
-(void) doSomething;
@end

@implementation OtherTestClass
-(void) doSomething {
    // ...
}
+(NSString *) instance {
    return [NSString stringWithFormat:@"Hello"];
}
@end

[[OtherTestClass instance] doSomething];

В данном случае компилятор также ничего не скажет, так как возвращаемое значение будет интерпритироваться как «id». Но вот во время выполнения приложения мы получим ошибку:

-[__NSCFString doSomething]: unrecognized selector sent to instance 0x102302a20

Приложение аварийно завершится.

Для того, чтобы избежать подобных проблем, был введет специальный тип «instancetype», который дает знать компилятору, что возвращаемый тип должен быть экземпляром этого же класса и, в отличие от «id», может быть использован только в качестве возвращаемого типа в объявлении метода.

Посмотрим на пример:

@interface TestClass: NSObject
+(instancetype) instance;
@end

@implementation TestClass
+ (instancetype) instance {
    return [[self alloc] init];
}
@end

@interface OtherTestClass: TestClass
-(void) doSomething;
@end

@implementation OtherTestClass
-(void) doSomething {
    // ...
}
@end

// ..

// Ошибка:  No visible @interface for 'TestClass' declares the selector 'doSomething'
[[[TestClass alloc] init] doSomething]; 

// // Ошибка:  No visible @interface for 'TestClass' declares the selector 'doSomething'
[[TestClass instance] doSomething];

Как видим, компилятор сгенерировал ошибку при выполнении «[[TestClass instance] doSomething]», так как с помощью «instancetype» мы указали, что метод должен возвращать тип экземпляра класса.

Если дочерний класс переопределят метод, возвращаемый тип «instancetype», то он должен возвращать экземпляр своего класса, а если это не так, то мы получим предупреждение:

@implementation OtherTestClass
-(void) doSomething {
}

// Method is expected to return an instance of its class type 'OtherTestClass', but is declared to return 'NSString *'
+(NSString *) instance {  
    return [NSString stringWithFormat:@"Hello"];
}
@end

или

@implementation OtherTestClass
-(void) doSomething {
    //...
}

+(instancetype) instance {
    // Incompatible pointer types returning 'NSString *' from a function with result type 'OtherTestClass *'
    return [NSString stringWithFormat:@"Hello"]; 
}
@end

Ключевое слово «instancetype» стоит использовать в качестве возвращаемого значения методов если:

  • метод возвращает только экземпляр своего класса и он является методом класса
  • метод начинается с «init», «autorelease», «retain», или «self» и является методом экземпляра
  • метод начинается с «new» или «alloc» и является методом класса
Ссылки:

Related result types

Автор: Антон Добкин

Поделиться

Клубный чат

В Slack-чате нашего клуба более 2000 участников. Присоединяйтесь к нам прямо сейчас!

slack-screen-640

Присоединиться

Для тех, кто уже с нами, вход здесь

Поддержать проект

Мы будем рады, если вы поддержите наш проект небольшим переводом, который пойдет на оплату хостинга и некоторых мелких расходов.

Яндекс-деньгами

Банковской картой

Свежие записи

  • Финальная программа 3-ей Международной конференции мобильных разработчиков MBLTdev 16 События
  • CocoaConf Belarus 2016 События
  • MO2016
    MobileOptimized 2016 События
  • Приглашаем на трансляцию WWDC 2016 в Санкт-Петербурге События

Следуйте за нами

Рубрики

  • Cocoa Developers Club
  • Dev Story
  • Libs-Pods-Tools
  • Swift
  • Архитектура
  • Дизайн
  • Новости
  • Приложения
  • Работа
  • Разработка для iOS
  • Разработка для OS X
  • События

Популярные теги

2gis app apple apps appstore cocoaconf CocoaConfBy cocoaheads dev English event ios ipad iphone Moscow mybook.ru objective-c osx PermissionScope Rambler&Co Rambler.iOS Scope swift UX wwdc xcode Yandex zvooq.ru Беларусь Владимир История клуба Минск Санкт-Петербург анимация вакансии видео встречи дизайн история разработки клуб компонент меню слайды события ссылки

Полезные ссылки

О нашем клубе
Чат нашего клуба
Правила клубного чата

© Cocoa Developers Club 2015