Ответ 1
Это то, о чем нас спрашивают. Мэтт ответил правильно, что переименование метода устранит проблему, но нам все еще остается вопрос, почему.
Ответ заключается в том, что Swift фактически переводит методы удобства в конструкторы.
Итак, пока нам нужно вызвать метод, подобный этому, в Objective-C:
[WXMediaMessage message];
В Swift мы должны просто называть WXMediaMessage()
. В Objective-C это эквивалентно вызову:
[[WXMediaMessage alloc] init];
Важно отметить, что фактический метод factory не вызывается, когда мы используем инициализатор по умолчанию Swift. Это произойдет прямо на init
без вызова обертки factory. Но лучшая практика предполагает, что наш метод factory должен быть не более чем [[self alloc] init];
. Одно замечательное предложение может быть одноточечным, но если у нас есть метод singleton factory, мы должны следовать шаблону, установленному Apple, и назовите наш метод factory с помощью "общего" префикса:
+ (instancetype)sharedMessage;
И следующий код Swift был бы абсолютно прав:
let message = WXMediaMessage.sharedMessage()
Но если message
не является одиночным, тогда он должен быть не более return [[self alloc] init];
, что мы получили бы со следующим кодом Swift:
let message = WXMediaMessage()
И вот что нам сообщает сообщение об ошибке:
'message()' is unavailable: use object construction 'WXMediaMessage()'
Сообщение об ошибке сообщает нам использовать инициализатор Swift по умолчанию, а не метод Objective-C factory. И это имеет смысл. Единственная причина, по которой когда-либо хотели методы factory в Objective-C, заключалась в том, что [[MyClass alloc] init]
выглядит уродливым. Вся наша инициализация все равно должна выполняться в методе init
, однако... не в методе factory, который мы создаем, потому что лучше не смотреть на alloc] init]
...
Рассмотрим следующий класс Objective-C:
@interface FooBar : NSObject
+ (instancetype)fooBar;
- (void)test;
@end
@implementation FooBar
- (instancetype)init {
self = [super init];
if (self) {
NSLog(@"initialized");
}
return self;
}
+ (instancetype)fooBar {
NSLog(@"initialized with fooBar");
return [[self alloc] init];
}
- (void)test {
NSLog(@"Testing FooBar");
}
@end
Теперь, используя следующий код Swift:
let var1 = FooBar()
var1.test()
Мы получаем следующий вывод:
2014-11-08 10:48:30.980 FooBar[5539:279057] initialized
2014-11-08 10:48:30.981 FooBar[5539:279057] Testing FooBar
Как мы видим, метод fooBar
никогда не вызывается. Но действительно, если вы строите свои классы Objective-C правильно с учетом хорошей практики, метод класса, названный как таковой, никогда не должен быть больше:
return [[self alloc] init];
И ваш метод init
должен обрабатывать все установленные настройки.
То, что происходит, становится более очевидным, когда мы используем менее простые инициализаторы и методы factory.
Рассмотрим, добавляем ли мы способ инициализации нашего класса числом:
@interface FooBar : NSObject
+ (instancetype)fooBarWithNumber:(int)number;
- (void)test;
@end
@implementation FooBar {
int _number;
}
- (instancetype)init {
self = [super init];
if (self) {
NSLog(@"initialized");
}
return self;
}
- (instancetype)initWithNumber:(int)number {
self = [self init];
if (self) {
NSLog(@"init with number: %i", number);
_number = number;
}
return self;
}
+ (instancetype)fooBarWithNumber:(int)number {
NSLog(@"fooBar with number: %i", number);
return [[self alloc] initWithNumber:number];
}
- (void)test {
NSLog(@"Testing FooBar (number: %i)", _number);
}
@end
В этот момент обратите внимание, что наш .h
предоставляет метод fooBarWithNumber:
, но не метод initWithNumber:
.
Теперь вернемся к коду Swift:
let var1 = FooBar(number: 3)
var1.test()
Swift превратил наш метод fooBarWithNumber:
в инициализатор: FooBar(number:Int32)
И вот вывод:
2014-11-08 10:57:01.894 FooBar[5603:282864] fooBar with number: 3
2014-11-08 10:57:01.894 FooBar[5603:282864] initialized
2014-11-08 10:57:01.895 FooBar[5603:282864] init with number: 3
2014-11-08 10:57:01.895 FooBar[5603:282864] Testing FooBar (number: 3)
Вызывается метод fooBarWithNumber:
, который вызывает initWithNumber:
, который вызывает init
.
Итак, ответ на вопрос "ПОЧЕМУ" заключается в том, что Swift переводит наши инициализаторы Objective-C и factory в инициализаторы стиля Swift.
Чтобы продолжить дальше, проблема связана не только с самим именем метода. Это комбинация имени метода, имени класса и типа возврата. Здесь мы использовали instancetype
как возвращаемый тип. Если мы используем instancetype
или наш тип определенного типа, мы сталкиваемся с проблемой. Но, однако, подумайте об этом:
@interface FooBar
+ (NSArray *)fooBar;
@end
И предположим некоторую допустимую реализацию этого метода класса, который соответствует имени класса, но тип возврата НЕ соответствует нашему классу.
Теперь следующий метод является абсолютно корректным кодом Swift:
let fooBarArray = FooBar.fooBar()
Поскольку тип возвращаемого значения не соответствует классу, Swift не может перевести его в инициализатор класса, поэтому наш метод в порядке.
Также стоит отметить, что если мы выберем id
как возвращаемый тип нашего метода, как таковой:
+ (id)fooBar;
Свифт позволит нам уйти от него... но он предупредит нас, что наша переменная выведена, чтобы иметь тип "AnyObject!". и рекомендует добавить явный тип, поэтому он рекомендовал бы это:
let var1: FooBar = FooBar.fooBar()
И это позволит нам это сделать... но наилучшая практика почти наверняка рекомендует не использовать id
в качестве возвращаемого типа. Apple недавно изменила почти все свои возвращаемые типы id
на instancetype
.