Как создать методы класса, которые соответствуют протоколу, совместно используемому 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 к методу, который не определил необязательный тип возвращаемого значения.

Другие вопросы по тегам