2017-02-03 70 views
9

FYI: Swift bug podniesione tutaj: https://bugs.swift.org/browse/SR-3871Nie można rzucić w Swift z Dowolnego? protokołu


Mam dziwny problem, gdzie obsada nie pracuje, ale konsola pokazuje go jako odpowiedniego typu.

Mam Public protocol

public protocol MyProtocol { } 

I zaimplementować to w module, z publiczną metodę, która zwraca instancję.

internal struct MyStruct: MyProtocol { } 

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

Wtedy, moim zdaniem regulatora, ja wywołać segue z tego obiektu jako nadawca

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

Wszystkie dobre do tej pory.

Problem jest w mojej metodzie prepare(for:sender:).

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

Jednak odlewanie instancji do MyProtocol zawsze zwraca nil.

Po uruchomieniu po sender as! MyProtocol w konsoli, pojawia się błąd Could not cast value of type '_SwiftValue' (0x1107c4c70) to 'MyProtocol' (0x1107c51c8). Jednak po sender wyświetli prawidłową instancję Module.MyStruct.

Dlaczego ten rzut nie działa?

(udało mi się go rozwiązać przez boks mój protokół w struct, ale chciałbym wiedzieć, dlaczego to nie działa jak jest, a jeśli nie ma lepszego sposobu, aby go naprawić)

+0

tutaj tylko kończy się tutaj, ale czy zmiana wewnętrznej deklaracji tutaj "wewnętrzna struktura MyStruct: MyProtocol {}' na "publiczne" coś zmienić? – Dennis

+0

@Dennis Nope :( – deanWombourne

Odpowiedz

13

To dość dziwny błąd - wygląda na to, że zdarza się, gdy instancja została zmostkowana do Obj-C przez boksowanie w _SwiftValue i jest statycznie wpisana jako Any(?). Ta instancja nie może być rzutowana na dany protokół, który jest zgodny z tym.

Według Joe Groff w komentarzach na bug report you filed:

Jest to przykład ogólnego „wykonawczym dynamicznego odlewu nie most most w razie potrzeby do protokołu” bug. Ponieważ nadawca jest postrzegany jako obiekt typu _SwiftValue, a my próbujemy uzyskać protokół, który nie jest zgodny, poddajemy się bez próbowania typu mostkowego.

Bardziej minimalny Przykładem może być:

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 

wystarczająco Niesamowicie pierwsze odlewy do AnyObject a następnie rzucając protokołu wydaje się działać:

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

Wygląda więc na to, że statycznie wpisując to jako AnyObject powoduje, że Swift sprawdza również typ zmostkowany dla zgodności z protokołem, umożliwiając rzutowanie, aby odniósł sukces. Rozumowanie tego, jak wyjaśniono w innym komentarzu Joe Groff jest:

Środowisko wykonawcze miał kilka błędów, gdzie to tylko próbuje pewne konwersje do jednego poziomu głębokości, ale nie po wykonywanie innych konwersji (tak AnyObject -> bridge -> Protokół może działać, ale Any -> AnyObject -> bridge -> Protocol does not). Jeśli to konieczne, to do pracy, w każdym razie, należy do pracy.

+2

Błąd zgłoszony tutaj: https://bugs.swift.org/browse/SR-3871 – deanWombourne

1

Problem polega na tym, że sender musi przejść przez świat Objective-C, ale Objective-C nie jest świadomy tej relacji protokół/struktura, ponieważ zarówno protokoły Swift, jak i struktury Swift są dla niej niewidoczne. Zamiast struktury, należy użyć klasy:

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

Teraz wszystko działa, jak można się spodziewać, ponieważ sender mogą żyć i oddychać w sposób spójny w świecie Objective-C.

+2

Nie mówię, że zachowanie, które wyizolowałeś, jest poprawne.Struktura jest boksowana, ale możesz myśleć, że jej związek z protokołem przetrwa to. Możesz chcieć złożyć błąd 'bugs.swift.org'. – matt

0

Oto moje rozwiązanie. Nie chciałem po prostu przekształcić go w class (re: this answer), ponieważ mój protokół jest implementowany przez wiele bibliotek i wszyscy musieliby o tym pamiętać.

Poszedłem do boksu mojego protokołu do struktury.

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) 
    } 
} 

Teraz po prostu przechodzę wokół instancji BoxedMyProtocol.