В версию 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
и является методом класса
Ссылки: