如果不应该,可选绑定会成功

Mar*_*n R 9 generics ios swift

这是我在Swift中发布的Traverse视图控制器层次结构的可能解决方案(稍加修改):

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
    }
}
Run Code Online (Sandbox Code Playgroud)

该方法应遍历父视图控制器层次结构并返回给定类的第一个实例,如果没有找到则返回nil.

但它不起作用,我无法弄清楚原因.标记为的可选绑定(XXX) 始终成功,因此即使第一个父视图控制器不是实例,也会返回第一个父视图控制器T.

这很容易重现:从Xcode 6 GM中的"iOS Master-Detail Application"模板创建项目,并viewDidLoad()MasterViewController类中添加以下代码:

if let vc = self.traverseAndFindClass(UICollectionViewController.self) {
    println("found: \(vc)")
} else {
    println("not found")
}
Run Code Online (Sandbox Code Playgroud)

selfMasterViewController(的子类UITableViewController),其父视图控制器是UINavigationController.有没有UICollectionViewController在父视图控制器层次,所以我希望该方法返回nil,并输出"未找到".

但这是发生的事情:

comparing <UINavigationController: 0x7fbc00c4de10> to UICollectionViewController
found: <UINavigationController: 0x7fbc00c4de10>
Run Code Online (Sandbox Code Playgroud)

这显然是错误的,因为UINavigationController它不是一个子类 UICollectionViewController.也许我犯了一些愚蠢的错误,但我找不到它.


为了隔离问题,我还试图用我自己的类层次结构重现它,独立于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")
}
Run Code Online (Sandbox Code Playgroud)

你猜怎么着?现在它按预期工作!输出是

comparing <MyApp.FirstSubClass: 0x7fff38f78c40> to MyApp.SecondSubClass
not found
Run Code Online (Sandbox Code Playgroud)

更新:

  • 删除泛型方法中的类型约束

    func traverseAndFindClass<T>(T.Type) -> T?
    
    Run Code Online (Sandbox Code Playgroud)

    正如@POB在评论中所建议的那样,它可以按预期工作.

  • 通过"两步绑定"替换可选绑定

    if let result = parentVC as Any as? T { // (XXX)
    
    Run Code Online (Sandbox Code Playgroud)

    正如@vacawama在他的回答中所建议的那样,它也能按预期工作.

  • 将构建配置从"Debug"更改为"Release"也会使该方法按预期工作.(到目前为止,我只在iOS模拟器中对此进行了测试.)

最后一点可能表明这是一个Swift编译器或运行时错误.我仍然无法看到为什么问题出现在子类中UIViewController,而不是我的子类BaseClass.因此,在接受答案之前,我会将问题保持开放一段时间.


更新2:Xcode 7开始修复此问题.

随着最终的Xcode 7发布,问题不再发生.现在if let result = parentVC as? T,traverseAndFindClass()方法中的可选绑定 在Release和Debug配置中按预期工作(并且失败).

vac*_*ama 3

如果您尝试有条件地将Playground 中的类型对象强制转换UINavigationController为 a :UICollectionViewController

var nc = UINavigationController()

if let vc = nc as? UICollectionViewController {
    println("Yes")
} else {
    println("No")
}
Run Code Online (Sandbox Code Playgroud)

你会得到这个错误:

Playground 执行失败::33:16:错误:如果让 vc = nc as,“UICollectionViewController”不是“UINavigationController”的子类型?UICollectionViewController {

但如果你这样做:

var nc = UINavigationController()

if let vc = (nc as Any) as? UICollectionViewController {
    println("Yes")
} else {
    println("No")
}
Run Code Online (Sandbox Code Playgroud)

它打印“否”。

所以我建议尝试:

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
    }
}
Run Code Online (Sandbox Code Playgroud)