2015-04-29 8 views
12

ja instacji do UIStoryboardSegue i za każdym razem próbuję użyć jednego z dwóch UIViews, Xcode jest Making Me dodać dwa opcjonalne rozpakowuje (!!), takie jak:Xcode Zmuszanie Swift Opcjonalny rozpakowuje dwukrotnie (!!)

let sourceView = self.sourceViewController.view 
sourceView!!.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight 

lub

let sourceView = self.sourceViewController.view! 
sourceView!.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight 

lub

self.sourceViewController.view!!.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight 

Zastanawiam się, czy ktoś może wyjaśnić, dlaczego tak jest.

+0

Xcode to miecz obosieczny. Ma problemy z rozpoznawaniem typów używanych w funkcjach teraz i ponieważ jest to niejawnie napisany język. – Garret

+0

@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. –

Odpowiedz

7

Właściwość UIStoryboardSeguesourceViewController 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.

+3

O nie, UIView‽ – jtbandes

+0

Dzięki za dokładną odpowiedź !! Miłość, która wie więcej niż to, co jest na powierzchni. –

0

Ponieważ

self.sourceViewController.view 

powrót rodzaj UIView?! Trzeba unwarp dwukrotnie, aby uzyskać UIView. Pierwszy unwarp ! następnie unwarp ?

let test:UIView?! = nil 
    let firstunwrap = test! //Here kind of firstwrap is UIView?,so need second wrap 
    let seconunwrap = firstunwrap! 

jeśli masz rodzaju UIView!?,

trzeba tylko unwarp once.because ! jest implicitly unwrapped optional

let test:UIView!? = nil 
    let firstunwrap = test!//Here firstwrap is UIView!,do not need to wrap anymore 
+0

Interesujące. Czy mógłbyś rozwinąć nieco więcej? Na przykład, w takim przypadku może być korzystne użycie '?! 'Iw takim przypadku drugie -'!? '?(<- to jest prawdziwy znak zapytania :-) – Unheilig

+0

Szczerze mówiąc, nigdy nie zaprojektowałem mojego kodu dla użytkownika?! lub!?, rzucę go na specjalną klasę tak wcześnie, jak to możliwe. – Leo

2

self.sourceViewController.view to rzadki przypadek "double-wrapped Opcjonalnie":

  • raz bo UIStoryboardSegue na sourceViewController jest wpisany jako AnyObject - do której wiadomość view może zostać wysłany, ponieważ typu sprawdziany stłumione, ale kosztem wracając opcjonalnego (jak wyjaśnię moją książkę: http://www.apeth.com/swiftBook/ch04.html#SECsuppressing)

  • i znowu ponieważ UIViewController może lub nie może mieć widokiem - rzeczywiście, UIViewController na view początkowo nil, aż widoku ładunku i viewDidLoad nazywa - tak własnością view sam jest wpisany jako opcjonalny

W ten sposób powstaje opcja Opcjonalnie zapakowana w Opcjonalne. Co mam zrobić w tej sytuacji, to napisać to tak w Swift 1.1 lub wcześniej:

let sourceView = (self.sourceViewController as UIViewController).view 

Albo z as! w Swift 1.2:

let sourceView = (self.sourceViewController as! UIViewController).view 

przygnębiony rozstrzyga wszelkie wątpliwości na raz. Teraz ustawienie sourceView.frame po prostu działa.

+1

'Właściwość' view' UIViewController nie jest opcjonalna. – zneak

+0

Punkt, który próbowałem przynieść, jest taki, że typem jest 'UIView?!', Ponieważ Swift wybrał selektor 'view' dla dynamicznej wysyłki, który zwraca' UIView? ', Zamiast własnego widoku' UIViewController', który nie jest opcjonalne. Ponadto 'sourceViewController' nie jest opcjonalny. – zneak

+0

@zneak Właściwie UIViewController 'view' Właściwość _jest_ opcjonalne. – matt