Как создать методы класса, которые соответствуют протоколу, совместно используемому Swift и Objective-C?
В последнее время я изучаю Swift.
Я решил написать гибридное приложение Swift/Objective-C, которое выполняло задачи, требующие больших вычислительных ресурсов, используя один и тот же алгоритм, реализованный на обоих языках.
Программа рассчитывает большой массив простых чисел.
Я определил протокол, которому должны соответствовать и версия Swift, и версия Objective-C объекта вычисления.
Оба объекта являются синглетонами, поэтому я создал типовой метод одноэлементного доступа в Objective-C:
+ (NSObject <CalcPrimesProtocol> *) sharedInstance;
Весь протокол выглядит так:
#import <Foundation/Foundation.h>
@class ComputeRecord;
typedef void (^updateDisplayBlock)(void);
typedef void (^calcPrimesCompletionBlock)(void);
@protocol CalcPrimesProtocol <NSObject>
- (void) calcPrimesWithComputeRecord: (ComputeRecord *) aComputeRecord
withUpdateDisplayBlock: (updateDisplayBlock) theUpdateDisplayBlock
andCompletionBlock: (calcPrimesCompletionBlock) theCalcPrimesCompletionBlock;
@optional //Without this @optional line, the build fails.
+ (NSObject <CalcPrimesProtocol> *) sharedInstance;
@end
Версия класса Objective-C реализует методы точно так, как определено выше, не беспокойтесь.
У быстрой версии есть метод:
class func sharedInstance() -> CalcPrimesProtocol
Однако, если я сделаю этот метод обязательным методом протокола, я получу сообщение об ошибке компилятора "Type ". CalcPrimesSwift не соответствует протоколу CalcPrimesProtocol.
Однако, если я отмечу метод синглтон-класса sharedInstance как необязательный в протоколе, он работает, и я могу вызвать этот метод либо в моем классе Swift, либо в моем классе Objective-C.
Я пропустил некоторые тонкости в определении моего метода класса Swift? Это кажется маловероятным, учитывая, что я могу вызывать метод класса sharedInstance() для моего класса Swift или класса Objective-C.
Вы можете скачать проект с Github и проверить его, если хотите. Это называется https://github.com/DuncanMC/SwiftPerformanceBenchmark.git. (ссылка на сайт)
1 ответ
В Objective-C мы всегда передавали указатели, а указатели всегда могли быть nil
, Многие программисты Objective-C использовали тот факт, что отправка сообщения nil
ничего не сделал и вернулся 0
/nil
/NO
, Быстрые ручки nil
совсем по другому. Объекты либо существуют (никогда nil
), или неизвестно, существуют ли они (где вступают в игру дополнительные опции Swift).
До Xcode 6.3 это означало, что любой код Swift, использующий любой код Objective-C, должен обрабатывать все ссылки на объекты как дополнительные функции Swift. Ничто в языковых правилах Objective-C не помешало указателю объекта быть nil
,
То, что это означало для использования протоколов Objective-C, классов и т. Д. От Swift, это то, что это был большой беспорядок. Нам пришлось выбирать между неидеальными решениями.
Учитывая следующий протокол Objective-C:
@protocol ObjCProtocol <NSObject>
@required + (id<ObjCProtocol>)classMethod;
@required - (id<ObjCProtocol>)instanceMethod;
@required - (void)methodWithArgs:(NSObject *)args;
@end
Мы можем либо принять определение метода как содержащее неявно развернутые опции:
class MyClass: NSObject, ObjCProtocol {
func methodWithArgs(args: NSObject!) {
// do stuff with args
}
}
Это делает полученный код более чистым (нам никогда не придется разворачивать его внутри тела), однако мы всегда будем подвергаться риску ошибки "найден ноль при развертывании необязательного".
Или, в качестве альтернативы, мы можем определить метод как истинный необязательный:
class MyClass: NSObject, ObjCProtocol {
func methodWithArgs(args: NSObject?) {
// unwrap do stuff with args
}
}
Но это оставляет нам много кода для распутывания беспорядка.
Xcode 6.3 исправляет эту проблему и добавляет "аннотации Nullability" для кода Objective-C.
Два недавно введенных ключевых слова nullable
а также nonnull
, Они используются там же, где вы объявляете тип возвращаемого значения или тип параметра для кода Objective-C.
- (void)methodThatTakesNullableOrOptionalTypeParameter:(nullable NSObject *)parameter;
- (void)methodThatTakesNonnullNonOptionalTypeParameter:(nonnull NSObject *)parameter;
- (nullable NSObject *)methodReturningNullableOptionalValue;
- (nonnull NSObject *)methodReturningNonNullNonOptionalValue;
В дополнение к этим двум ключевым словам аннотации, Xcode 6.3 также представляет набор макросов, который позволяет легко пометить большие разделы кода Objective C как nonnull
(файлы без аннотаций вообще принимаются за nullable
). Для этого мы используем NS_ASSUME_NONNULL_BEGIN
в верхней части раздела и NS_ASSUME_NONNULL_END
в нижней части раздела мы хотим отметить.
Так, например, мы могли бы обернуть весь ваш протокол в эту пару макросов.
NS_ASSUME_NONNULL_BEGIN
@protocol CalcPrimesProtocol <NSObject>
- (void) calcPrimesWithComputeRecord: (ComputeRecord *) aComputeRecord
withUpdateDisplayBlock: (updateDisplayBlock) theUpdateDisplayBlock
andCompletionBlock: (calcPrimesCompletionBlock) theCalcPrimesCompletionBlock;
+ (id <CalcPrimesProtocol> ) sharedInstance;
@end
NS_ASSUME_NONNULL_END
Это имеет тот же эффект, что и маркировка всех параметров указателя и возвращаемых типов как nonnull
( за некоторыми исключениями, как отмечается в этой записи в блоге Apple Swift).
Pre-Xcode 6.3
Класс Swift, соответствующий протоколу Objective-C, должен обрабатывать любые типы Objective-C в этом протоколе как необязательные.
Пытаясь понять это, я создал следующий протокол Objective-C:
@protocol ObjCProtocol <NSObject>
@required + (id<ObjCProtocol>)classMethod;
@required - (id<ObjCProtocol>)instanceMethod;
@required - (void)methodWithArgs:(NSObject *)args;
@end
И затем, создал класс Swift, который унаследовал от NSObject
и заявил, что соответствует этому ObjCProtocol
,
Затем я набрал эти имена методов и позволил Swift автоматически завершить методы для меня, и вот что я получил (я вставил в тела методов, остальное, если автозаполнение):
class ASwiftClass : NSObject, ObjCProtocol {
class func classMethod() -> ObjCProtocol! {
return nil
}
func instanceMethod() -> ObjCProtocol! {
return nil
}
func methodWithArgs(args: NSObject!) {
// do stuff
}
}
Теперь мы можем использовать обычные опционные ?
) вместо этих автоматически развернутых опций, если мы хотим. Компилятор совершенно доволен либо. Дело в том, что мы должны учитывать возможность nil
потому что протоколы Objective-C не могут предотвратить nil
от того, чтобы быть переданным.
Если бы этот протокол был реализован в Swift, мы могли бы выбрать, является ли возвращаемый тип необязательным или нет, и Swift помешает нам вернуться nil
к методу, который не определил необязательный тип возвращаемого значения.