La binding facultative réussit si elle ne doit pas

C'est ce que j'ai posté comme une solution possible à la hiérarchie du controller de Traverse View dans Swift (légèrement modifié):

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

La méthode doit parcourir la hiérarchie du controller de vue parent et renvoyer la première instance de la class donnée, ou zéro si aucune n'est trouvée.

Mais ça ne marche pas, et je n'arrive pas à comprendre pourquoi. La binding facultative marquée avec (XXX) réussit toujours , de sorte que le premier controller de vue parent est renvoyé même s'il ne s'agit pas d'une instance de T

Cela peut facilement être reproduit: Créez un projet à partir du model "iOS Master-Detail Application" dans Xcode 6 GM, et ajoutez le code suivant à viewDidLoad() de la class MasterViewController :

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

self est un MasterViewController (une sous-class de UITableViewController ), et son controller de vue parent est un UINavigationController . Il n'y a pas de UICollectionViewController dans la hiérarchie des controllers parents, donc je m'attendrais à ce que la méthode renvoie nil et que la sortie soit "introuvable".

Mais voici ce qui arrive:

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

C'est évidemment faux, car UINavigationController n'est pas une sous-class de UICollectionViewController . Peut-être que j'ai fait une erreur stupide, mais je ne pouvais pas le find.


Afin d'isoler le problème, j'ai aussi essayé de le reproduire avec ma propre hiérarchie de classs, indépendante d'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") } 

Et devine quoi? Maintenant, cela fonctionne comme prévu! La sortie est

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

METTRE À JOUR:

  • Suppression de la contrainte de type dans la méthode générique

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

    comme suggéré par @POB dans un commentaire le fait fonctionner comme prévu.

  • Remplacer la binding facultative par une "binding en deux étapes"

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

    comme suggéré par @vacawama dans sa réponse, il fonctionne aussi comme prévu.

  • Changer la configuration de construction de "Debug" à "Release" rend également la méthode fonctionne comme prévu. (J'ai testé cela uniquement dans le simulateur iOS jusqu'à présent.)

Le dernier point peut indiquer qu'il s'agit d'un compilateur Swift ou d'un bogue d'exécution. Et je ne vois toujours pas pourquoi le problème se produit avec les sous-classs de UIViewController , mais pas avec les sous-classs de ma BaseClass . Par conséquent, je vais garder la question ouverte pendant un moment avant d'accepter une réponse.


UPDATE 2: Ceci a été corrigé à partir de Xcode 7 .

Avec la version finale de Xcode 7, le problème ne se produit plus. La binding facultative if let result = parentVC as? T if let result = parentVC as? T dans la méthode traverseAndFindClass() fonctionne maintenant (et échoue) comme prévu, à la fois dans la configuration Release et Debug.

Si vous tentez de UINavigationController de manière conditionnelle un object de type UINavigationController en UICollectionViewController dans un Playground:

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

Vous obtenez cette erreur:

L'exécution du Playground a échoué:: 33: 16: erreur: 'UICollectionViewController' n'est pas un sous-type de 'UINavigationController' si let vc = nc as? UICollectionViewController {

mais si à la place vous faites:

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

il imprime "Non".

Donc, je suggère d'essayer:

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