Неудача в Swift из любого? к протоколу

FYI: Swift ошибка здесь: https://bugs.swift.org/browse/SR-3871


У меня возникла странная проблема, когда актер не работает, но консоль показывает его как правильный тип.

У меня есть общедоступный протокол

public protocol MyProtocol { }

И я реализую это в модуле с помощью общедоступного метода, который возвращает экземпляр.

internal struct MyStruct: MyProtocol { }

public func make() -> MyProtocol { return MyStruct() }

Затем, на мой взгляд, контроллер, я запускаю segue с этим объектом в качестве отправителя

let myStruct = make()
self.performSegue(withIdentifier: "Bob", sender: myStruct)

Пока все хорошо.

Проблема заключается в моем методе prepare(for:sender:).

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "Bob" {
        if let instance = sender as? MyProtocol {
            print("Yay")
        }
    }
}

Однако приведение экземпляра в MyProtocol всегда возвращает nil.

Когда я запускаю po sender as! MyProtocol в консоли, это дает мне ошибку Could not cast value of type '_SwiftValue' (0x1107c4c70) to 'MyProtocol' (0x1107c51c8). Однако po sender выдаст действительный экземпляр Module.MyStruct.

Почему эта работа не работает?

(мне удалось решить это, поместив мой протокол в структуру, но я хотел бы знать, почему он не работает, как есть, и если есть лучший способ его исправить)

Ответ 1

Это довольно странная ошибка - похоже, что это происходит, когда экземпляр был соединен с Obj-C, помещенный в _SwiftValue и статически напечатан как Any(?). Этот экземпляр не может быть передан в данный протокол, который он соответствует.

По словам Джо Гроффа в комментариях отчета об ошибке который вы зарегистрировали:

Это экземпляр общей "динамической кастинга времени выполнения, которая не перехватывает, если необходимо, для подключения к протоколу". Поскольку отправитель рассматривается как тип объекта _SwiftValue, и мы пытаемся перейти к протоколу, который он не соответствует, мы отказываемся, не пробовав также мостовой тип.

Более минимальным примером может быть:

protocol P {}
struct S : P {}

let s = S()

let val : Any = s as AnyObject // bridge to Obj-C as a _SwiftValue.

print(val as? P) // nil

Как ни странно, первое нажатие на AnyObject и последующее отключение к протоколу работают:

print(val as AnyObject as! P) // S()

Таким образом, кажется, что статическая типизация в качестве AnyObject заставляет Swift также проверять мостовой тип для соответствия протокола, что позволяет добиться успеха. Причиной этого, как объясняется в другом комментарии Джо Гроффа, является:

В среде исполнения было множество ошибок, где она только пытается выполнить определенные преобразования на один уровень глубины, но не после выполнения других преобразований (так что AnyObject → bridge → Protocol может работать, но Any → AnyObject → bridge → Протокол не работает). Он должен работать, во всяком случае.

Ответ 2

Проблема заключается в том, что sender должен проходить через мир Objective-C, но Objective-C не знает об этом отношении между протоколом и структурой, поскольку как Swift-протоколы, так и Swift-структуры невидимы для него. Вместо структуры используйте класс:

protocol MyProtocol {}
class MyClass: MyProtocol { }
func make() -> MyProtocol { return MyClass() }

Теперь все работает так, как вы ожидаете, потому что sender может жить и дышать когерентно в мире Objective-C.

Ответ 3

Вот мое решение. Я не хотел просто превращать его в class (re: этот ответ), потому что мой протокол реализуется несколькими библиотеками, и все они должны помнить для этого.

Я пошел на бокс мой протокол в структуру.

public struct BoxedMyProtocol: MyProtocol {
    private let boxed: MyProtocol

    // Just forward methods in MyProtocol onto the boxed value
    public func myProtocolMethod(someInput: String) -> String {
        return self.boxed.myProtocolMethod(someInput)
    }
}

Теперь я просто просматриваю экземпляры BoxedMyProtocol.

Ответ 4

Все еще не исправлено. Мой любимый и самый простой обходной путь - на сегодняшний день цепное литье:

if let instance = sender as AnyObject as? MyProtocol {

}