可选绑定成功,如果它不应该 [英] Optional binding succeeds if it shouldn't
问题描述
这是我发布的一个可能的解决方案,用于在Swift中遍历视图控制器层次结构(略微修改):
$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $' UIViewController>(T.Type) - > T' {
var currentVC = self
while let parentVC = currentVC.parentViewController {
println(比较\(parentVC)到\(T.description()))
如果让结果= parentVC为? T {//(XXX)
返回结果
}
currentVC = parentVC
}
返回零
}
}
该方法应该遍历父视图控制器层次结构并返回给定类的第一个
实例,或者如果没有找到零。
但它不起作用,我不知道为什么。用(XXX)
标记的可选绑定
始终会成功,以便第一个父视图控制器返回
,即使它不是 T
的实例。
这可以很容易地复制:从iOS Master - 详细应用程序
模板,将以下代码添加到
MasterViewController
的
viewDidLoad()
/ code> class:
如果让vc = self.traverseAndFindClass(UICollectionViewController.self){
println (found:\(vc))
} else {
println(not found)
}
self 是
),它的 MasterViewController
(<$ c的子类$ c> UITableViewController
父视图控制器是一个 UINavigationController
。
父视图
控制器层次结构中没有 UICollectionViewController
,所以我期望方法
返回 nil
,输出为not found。
但是,这会发生什么情况:
比较< UINavigationController:0x7fbc00c4de10>到UICollectionViewController
找到:< UINavigationController:0x7fbc00c4de10>
这显然是错误的,因为 UINavigationController
是不是
UICollectionViewController
的子类。也许我犯了一些愚蠢的错误,但我找不到它。
为了找出问题,我也尝试过使用我自己的类
层次结构重现它,独立于UIKit:
class BaseClass:NSObject {
var parentViewController:BaseClass?
}
class FirstSubClass:BaseClass {}
$ b $ class SecondSubClass:BaseClass {}
extension BaseClass {
func traverseAndFindClass< T where T:BaseClass>(T.Type) - > T' {
var currentVC = self
while let parentVC = currentVC.parentViewController {
println(比较\(parentVC)到\(T.description()))
如果让结果= parentVC为? T {//(XXX)
返回结果
}
currentVC = parentVC
}
返回零
}
}
let base = BaseClass()
base.parentViewController = FirstSubClass()
如果让result = base.traverseAndFindClass(SecondSubClass.self){
println(found :\(result))
} else {
println(not found)
}
猜猜是什么?现在它按预期工作!输出结果是:
比较< MyApp.FirstSubClass:0x7fff38f78c40>到MyApp.SecondSubClass
未找到
更新:
-
删除泛型方法中的类型约束
func traverseAndFindClass< T>(T.Type) - > T'
正如@POB在评论中所建议的那样,它可以按预期工作。
-
通过两步绑定替换可选绑定
if let result = parentVC as Any as?在他的回答中,@vacawama建议的T {//(XXX)
也可以按预期工作。
- 将构建配置从调试更改为发布也会使
方法按预期工作。 (我已经在iOS模拟器中测试过)。
$ b最后一点可能表明这是一个Swift编译器或运行时错误。我仍然
不知道为什么问题发生在UIViewController
的子类中,但是没有
和我的BaseClass 子类。 code>。因此,在接受答案之前,我会保持问题的答案为
现在可以按照预期工作(并且失败),无论是在Release还是Debug配置中。
。
更新2 : Xcode 7 已被修正。
在最终的Xcode 7
版本中,再也发生了。可选绑定
if let result = parentVC as? traverseAndFindClass()
方法
中的T解决方案如果您尝试有条件地将
UINavigationController
类型的对象转换为UICollectionViewController
在Playground中:
var nc = UINavigationController()
如果让vc = nc as? UICollectionViewController {
println(Yes)
} else {
println(No)
}
你得到这个错误:
Playground执行失败:33:16:error:'UICollectionViewController'不是'UINavigationController'
的子类型,如果让vc = nc as? UICollectionViewController {
但是如果您改为:
var nc = UINavigationController()
如果让vc =(nc as Any)as? UICollectionViewController {
println(Yes)
} else {
println(No)
}
打印出No。
所以我建议您尝试:
扩展UIViewController {
func traverseAndFindClass< T where T:UIViewController>(T.Type) - > T' {
var currentVC = self
while let parentVC = currentVC.parentViewController {
println(比较\(parentVC)到\(T.description()))
如果让结果=(parentVC as Any)as? T {//(XXX)
返回结果
}
currentVC = parentVC
}
返回零
}
}
This is what I posted as a possible solution to Traverse view controller 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 should traverse up the parent view controller hierarchy and return the first instance of the given class, or nil if none is found.
But it does not work, and I cannot figure out why. The optional binding marked with
(XXX)
always succeeds, so that the first parent view controller is returned even if it is not an instance ofT
.This can easily be reproduced: Create a project from the "iOS Master-Detail Application" template in Xcode 6 GM, and add the following code to
viewDidLoad()
of theMasterViewController
class:if let vc = self.traverseAndFindClass(UICollectionViewController.self) { println("found: \(vc)") } else { println("not found") }
self
is aMasterViewController
(a subclass ofUITableViewController
), and its parent view controller is aUINavigationController
. There is noUICollectionViewController
in the parent view controllers hierarchy, so I would expect that the method returnsnil
and the output is "not found".But this is what happens:
comparing <UINavigationController: 0x7fbc00c4de10> to UICollectionViewController found: <UINavigationController: 0x7fbc00c4de10>
This is obviously wrong, because
UINavigationController
is not a subclass ofUICollectionViewController
. Perhaps I made some stupid error, but I could not find it.
In order to isolate the problem, I also tried to reproduce it with my own class hierarchy, independent 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! The output is
comparing <MyApp.FirstSubClass: 0x7fff38f78c40> to MyApp.SecondSubClass not found
UPDATE:
Removing the type constraint in the generic method
func traverseAndFindClass<T>(T.Type) -> T?
as suggested by @POB in a comment makes it work as expected.
Replacing the optional binding by a "two-step binding"
if let result = parentVC as Any as? T { // (XXX)
as suggested by @vacawama in his answer also makes it work as expected.
- Changing the build configuration from "Debug" to "Release" also makes the method work as expected. (I have tested this only in the iOS Simulator so far.)
The last point could indicate that this is a Swift compiler or runtime bug. And I still cannot see why the problem occurs with subclasses of
UIViewController
, but not with subclasses of myBaseClass
. Therefore I will keep the question open for a while before accepting an answer.
UPDATE 2: This has been fixed as of Xcode 7.
With the final Xcode 7 release the problem does not occur anymore. The optional binding
if let result = parentVC as? T
in thetraverseAndFindClass()
method now works (and fails) as expected, both in Release and Debug configuration.解决方案If you try to conditionally cast an object of type
UINavigationController
to aUICollectionViewController
in a Playground:var nc = UINavigationController() if let vc = nc as? UICollectionViewController { println("Yes") } else { println("No") }
You get this error:
Playground execution failed: :33:16: error: 'UICollectionViewController' is not a subtype of 'UINavigationController' if let vc = nc as? UICollectionViewController {
but if instead you do:
var nc = UINavigationController() if let vc = (nc as Any) as? UICollectionViewController { println("Yes") } else { println("No") }
it prints "No".
So 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 } }
这篇关于可选绑定成功,如果它不应该的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!