Optional binding succeeds if it should not

This is what I posted as a possible solution to the view manager hierarchy in Swift (slightly modified):

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

The method must go through the hierarchy of the parent view controller and return the first instance of this class, or nil if none are found.

But this does not work, and I cannot understand why. An extra binding marked with a sign (XXX) always succeeds, so the first parent view controller is returned even if it is not an instance of T

This can be easily reproduced: Create a project from the "Applications with main iOS applications" template in Xcode 6 GM and add the following code to the viewDidLoad() MasterViewController class:

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

self is a MasterViewController (a subclass of UITableViewController ), and its parent view controller is UINavigationController . The parent view does not have a UICollectionViewController hierarchy of controllers, so I expect the method to return nil and the output "not found".

But here is what happens:

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

This is obviously wrong because the UINavigationController not a subclass of the UICollectionViewController . Perhaps I made some stupid mistake, but I could not find it.


To isolate the problem, I also tried to reproduce it with my own hierarchy class, regardless of 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") } 

And guess what? Now it works as expected! Output signal

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

UPDATE:

  • Removing a type constraint in a generic method

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

    as suggested by @POB in the comment, makes it work properly.

  • Replace an optional snap using a two-step snap

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

    as suggested by @vacawama in his answer, also makes it work properly.

  • Changing the assembly configuration from "Debug" to "Vacation" also makes the method work properly. (I only tested this on iOS Simulator so far.)

The last point may indicate that it is a Swift compiler or a runtime error. And I still can not understand why the problem occurs with subclasses of UIViewController , but not with subclasses of my BaseClass . Therefore, I will keep the question open for before accepting the answer.


UPDATE 2: This is fixed as Xcode 7 .

With the final Xcode 7 release the problem no longer occurs. Additional binding if let result = parentVC as? T if let result = parentVC as? T in the traverseAndFindClass() method now works (and does not work), as expected, both in the Release and Debug configurations.

+8
generics ios swift
source share
1 answer

If you try to conditionally drop an object of type UINavigationController on a UICollectionViewController in the playground:

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

You will get this error:

Playground execution failed: 33:16: error: "UICollectionViewController" is not a subtype of "UINavigationController" if let vc = nc how? UICollectionViewController {

but if you do this:

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

he prints "No."

Therefore, I suggest trying:

 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 } } 
+3
source share

All Articles