antonio's blog

antonio's blog


Блог о всяком разном, связанном с разработкой ПО. Пишу редко, когда есть время и желание.

Anton Dobkin
Author

Share


Tags


Возвращаемый тип instancetype

Anton DobkinAnton Dobkin

В версию 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 стоит использовать в качестве возвращаемого значения методов если:

Ссылки:

[1] Related result types

Anton Dobkin
Author

Anton Dobkin

Comments