2014-09-14 15 views
8

Questo è quello che ho postato come una possibile soluzione per Traverse view controller hierarchy in Swift (leggermente modificato):vincolante opzionale riesce se non dovrebbe

extension UIViewController { 

    func traverseAndFindClass<T where T : UIViewController>(T.Type) -> T? { 
     var currentVC = self 
     while let parentVC = currentVC.parentViewController { 
      println("comparing \(parentVC) to \(T.description())") 
      if let result = parentVC as? T { // (XXX) 
       return result 
      } 
      currentVC = parentVC 
     } 
     return nil 
    } 
} 

Il metodo dovrebbe attraversare la gerarchia vista padre controller e restituire il prima istanza della classe data, o zero se non ne viene trovato nessuno.

Ma non funziona, e non riesco a capire perché. Il binding facoltativo contrassegnato con (XXX)ha sempre successo su, in modo che il primo controller di visualizzazione padre venga restituito anche se non è un'istanza di T.

Questo può essere facilmente riprodotti: Creare un progetto dal "iOS Master-Detail Application" modello in Xcode 6 GM, e aggiungere il seguente codice viewDidLoad() della classe MasterViewController:

if let vc = self.traverseAndFindClass(UICollectionViewController.self) { 
    println("found: \(vc)") 
} else { 
    println("not found") 
} 

self è un MasterViewController (una sottoclasse di UITableViewController) e il suo controller di visualizzazione genitore è un UINavigationController. Non c'è il UICollectionViewController nella gerarchia dei controllori , quindi mi aspetto che il metodo restituisca nil e l'output sia "non trovato".

Ma questo è ciò che accade:

comparing <UINavigationController: 0x7fbc00c4de10> to UICollectionViewController 
found: <UINavigationController: 0x7fbc00c4de10> 

Questo è ovviamente sbagliato, perché UINavigationController non è una sottoclasse di UICollectionViewController. Forse ho fatto qualche errore stupido, ma non sono riuscito a trovarlo.


Al fine di isolare il problema, ho anche provato a riprodurlo con la mia propria gerarchia delle classi , indipendentemente UIKit:

class BaseClass : NSObject { 
    var parentViewController : BaseClass? 
} 

class FirstSubClass : BaseClass { } 

class SecondSubClass : BaseClass { } 

extension BaseClass { 

    func traverseAndFindClass<T where T : BaseClass>(T.Type) -> T? { 
     var currentVC = self 
     while let parentVC = currentVC.parentViewController { 
      println("comparing \(parentVC) to \(T.description())") 
      if let result = parentVC as? T { // (XXX) 
       return result 
      } 
      currentVC = parentVC 
     } 
     return nil 
    } 
} 

let base = BaseClass() 
base.parentViewController = FirstSubClass() 

if let result = base.traverseAndFindClass(SecondSubClass.self) { 
    println("found: \(result)") 
} else { 
    println("not found") 
} 

E indovinate un po '? Ora funziona come previsto! L'uscita è

comparing <MyApp.FirstSubClass: 0x7fff38f78c40> to MyApp.SecondSubClass 
not found 

UPDATE:

  • rimozione del vincolo di tipo nel metodo generico

    func traverseAndFindClass<T>(T.Type) -> T? 
    

    come suggerito da @POB in un commento lo fa funzionare come previsto.

  • Sostituzione del legame opzionale da un

    if let result = parentVC as Any as? T { // (XXX) 
    

    "vincolante in due fasi", come suggerito dal @vacawama nella sua risposta rende inoltre funziona come previsto.

  • La modifica della configurazione di costruzione da "Debug" a "Rilascio" rende anche il metodo come previsto. (L'ho provato finora solo su iOS Simulator.)

L'ultimo punto potrebbe indicare che si tratta di un compilatore Swift o di un bug di runtime. E io ancora non vedo perché il problema si verifica con sottoclassi di UIViewController, ma non con sottoclassi del mio BaseClass. Pertanto terrò aperta la domanda per un prima di accettare una risposta.


UPDATE 2: Questo è stato fissato al Xcode 7.

Con il rilascio Xcode 7 finale il problema non si verifica più. L'associazione facoltativa if let result = parentVC as? T nel metodo traverseAndFindClass() ora funziona (e non riesce) come previsto, sia nella configurazione di rilascio che in quella di debug.

+1

Se si tenta 'func traverseAndFindClass (T.Type) -> T? {/*...*/} 'invece di' func traverseAndFindClass (T.Type) -> T? {/*...*/} ', stamperà" Not found "per' if let vc = self.traverseAndFindClass (UICollectionViewController.self) '. Tuttavia, 'if let vc = self.traverseAndFindClass (UIViewController.self)' e 'se vc = self.traverseAndFindClass (UINavigationController.self)' stamperà entrambi "Trovato: ". –

+0

@POB: Funziona davvero (come fa la soluzione suggerita nella risposta di vacawama), ma non riesco ancora a capire * perché *. –

+0

Potrebbe essere qualcosa di oscuro, hai provato a chiedere sui forum dev? –

risposta

3

Se si tenta di lanciare condizionalmente un oggetto di tipo UINavigationController ad un UICollectionViewController in un parco giochi:

var nc = UINavigationController() 

if let vc = nc as? UICollectionViewController { 
    println("Yes") 
} else { 
    println("No") 
} 

Si ottiene questo errore:

Playground esecuzione fallita:: 33: 16: errore: 'UICollectionViewController' non è un sottotipo di 'UINavigationController' se lascia vc = nc come? UICollectionViewController {

ma se invece lo fai:

var nc = UINavigationController() 

if let vc = (nc as Any) as? UICollectionViewController { 
    println("Yes") 
} else { 
    println("No") 
} 

esso stampa "No".

Quindi vi suggerisco di provare:

extension UIViewController { 

    func traverseAndFindClass<T where T : UIViewController>(T.Type) -> T? { 
     var currentVC = self 
     while let parentVC = currentVC.parentViewController { 
      println("comparing \(parentVC) to \(T.description())") 
      if let result = (parentVC as Any) as? T { // (XXX) 
       return result 
      } 
      currentVC = parentVC 
     } 
     return nil 
    } 
} 
+0

In effetti funziona come previsto (come fa la soluzione suggerita da @POB in un commento), ma non riesco ancora a capire * perché *. E qual è la differenza rispetto al mio secondo esempio di BaseClass/FirstSubClass/SecondSubClass? Btw. 'se let risultato = (parentVC come AnyObject) come? T' does * not * funziona. –

+0

Lo stesso problema si verifica ancora con l'ultimo Xcode 6.3.1, e il workaround funziona ancora. Sembra che non ci sia una soluzione migliore! –

Problemi correlati