Właściwość UIStoryboardSegue
sourceViewController
jest wpisany jako AnyObject
i jako cecha zgodności Objective-C, po import Foundation
, robi się trochę dziwne z AnyObject
.
Zamiast szukać metod na typie AnyObject
, Swift szuka zamiast niego selektorów Objective-C, dokładnie tak jak w przypadku Objective-C z typem id
. Każdy selektor z dowolnej klasy jest uczciwą grą: jeśli chcesz, możesz spróbować wywołać activeProcessorCount
na swoim obiekcie, nawet jeśli jest to selektor NSProcessInfo
, a kompilator pozwoli ci to zrobić. (Z oczywistych względów zepsułoby to się w czasie wykonywania.) Nazywa się to dynamiczną wysyłką, w przeciwieństwie do wysyłki statycznej (normalny mechanizm wywoływania w Swift).
Jedną z cech dynamicznego wysyłania jest to, że zawsze dodaje warstwę niejawnego zawijania. Jeśli masz właściwość Objective-C, która zwraca wartość String
, dynamiczna wysyłka sprawi, że zwróci ona String!
.
Rzeczy stają się owłosione, gdy wiele klas deklaruje selektory o tej samej nazwie, ale o różnych typach powrotu (lub o różnych parametrach, ale nie jesteśmy zainteresowani tym przypadkiem). Nie wiem, w jaki sposób kompilator wybiera który selektor spośród wielu identycznie nazwanych, o których wie. Tak czy inaczej, wybiera jeden, i utkniesz z nim, chyba że rzucisz obiekt na bardziej precyzyjny typ, w którym to przypadku kompilator Swift pozwoli ci tylko użyć statycznej wysyłki.
Korzystanie swiftc
„s -dump-ast
argumentu, widzimy LISP-like reprezentację jak kompilator analizowany wyrażeniem:
(pattern_binding_decl
(pattern_named type='UIView?!' 'sourceView')
(dynamic_member_ref_expr type='UIView?!' location=MySegue.swift:15:46 range=[MySegue.swift:15:25 - line:15:46] decl=UIKit.(file).UIGestureRecognizer.view
(member_ref_expr type='AnyObject' location=MySegue.swift:15:25 range=[MySegue.swift:15:25 - line:15:25] decl=UIKit.(file).UIStoryboardSegue.sourceViewController
(derived_to_base_expr implicit type='UIStoryboardSegue' location=MySegue.swift:15:20 range=[MySegue.swift:15:20 - line:15:20]
(declref_expr type='Segue' location=MySegue.swift:15:20 range=[MySegue.swift:15:20 - line:15:20] decl=xxx.(file).Segue.func [email protected]:14:7 specialized=no)))))
Istnieje wiele cruft, ale widać, że wygenerowała dynamic_member_ref_expr
zamiast z member_ref_expr
. Jeśli przewiniesz całą drogę w prawo do atrybutu "decl
", zobaczysz, że używa ona własności UIGestureRecognizer
's, (declared as UIView?
), więc wyrażenie zwraca wartość UIView?!
.
W przeciwieństwie do tego, właściwość UIViewController
o wartości view
jest zadeklarowana jako UIView!
. Jeśli kompilator wybrałby ten selektor zamiast niego, otrzymalibyśmy UIView!!
, a potrzebowałbyś tylko jednego poziomu jawnego rozpakowania.
Możesz (i musisz, w takich przypadkach, jak ten) jawnie rozwinąć niejawnie-nieopakowane wartości. Z numerem sourceView
jako UIView?!
pierwszy !
odwija kod UIView?!
do UIView?
, a drugi odwija kod UIView?
w ostatecznie użyteczny UIView
. Dlatego potrzebujesz dwóch wykrzykników.
Klasa deklarująca selektor używany do dynamicznej wysyłki jest nieistotna, dopóki obiekt docelowy implementuje ją, akceptuje zgodne argumenty i zwraca zgodny typ. UIView?
i UIView!
są kompatybilne na poziomie binarnym, więc na końcu program nadal działa. Jeśli jednak spodziewałeś się, że masz String
lub inny niepowiązany typ, możesz być zaskoczony. Moim zdaniem, należy unikać dynamicznej wysyłki tak bardzo, jak to tylko możliwe.
tl; dr: jeśli rzucisz sourceViewController
do UIViewController
, można uzyskać prawidłową definicję view
własność i nie będzie trzeba go rozpakować w ogóle.
Xcode to miecz obosieczny. Ma problemy z rozpoznawaniem typów używanych w funkcjach teraz i ponieważ jest to niejawnie napisany język. – Garret
@Garret faktycznie nie jest to XCode, ale struktura kompilatora, która ma te wady. I prawdopodobnie będziemy musieli z tym żyć przez dłuższy czas. –