Неудача в 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 {
}