理解swift中UIWindow的两次解包

这两天开始尝试用 Swift 代替 OC 写代码了,碰到了许多坑,也慢慢地一个个跳了出来。不过当我用AppDelegatewindow属性来获取NavigationController,看到windows??这么诡异的写法的时候,我还是晕了,这特么什么玩意?为什么这里要两个问号?

1
2
3
let nvc = UIApplication.sharedApplication().delegate?.window??.rootViewController as? UINavigationController {
// ...
}

我特意去掉window后面的一个问号,结果 Xcode 就报错了,提示我window还没有解包,可我明明加了一个问号了呀,为什么非要两个?
我尝试重新写了一遍这句代码,写到delegate?. 时,Xcode 的代码提示那边显示window这个属性是UIWindow??类型的。如果windowUIWindow??的,那么上面那句代码window后面用两个问号也就说得通了,原来UIWindow套了两层Optional的壳,那是要解两次包。

但是问题又来了,我回过头去看AppDelegate.swiftwindow属性的定义:

1
var window: UIWindow?

明明window属性的类型是UIWindow?,怎么莫名其妙就变成了UIWindow??呢?

再往上翻UIApplicationDelegate这个协议API,可以找到window属性的声明:

1
2
@available(iOS 5.0, *)
optional public var window: UIWindow? { get set }

window属性是的类型确实为UIWindows?,但是同时它是optional的,即实现 UIApplicationDelegate协议的遵循者类可以选择不实现这个属性。
隐约觉得这里好像是这里有点问题,但我还是没想明白。后来去 StackOverFlow 上找了下相关的问题,有好几个人遇到了和我一样的疑惑。仔细的看了一下大神们的回答,才大概理解了这是怎么回事。

一般来说,Swift 中协议规定的属性或者方法等都需要被实现,不像 OC 中可以使用@optional来定义一些遵循者类可以选择性实现的方法。当然这只是一般情况,Swift 中要设置可选成员也是可以的,那就是像上面的window属性一样,在前面加上optional关键字。但是这个是要条件的,即可选的协议只能在含有@objc前缀的协议中生效,而且@objc协议只能被类遵循。而且遵循者类必须是NSObject的子类,或者如果不是的话,也必须在该可选属性或者方法前面加上@objc,像下面这样:

1
2
3
4
5
6
7
8
9
@objc protocal Flier {
optional func sing()
}

class Bird: Flier {
@objc func sing() {
print("tweet")
}
}

在协议成员前面加了optional关键字,其实就约等于包装了一层optional。为什么会这样呢?
Swift 的协议成员可以设为可选,其实就是为了兼容 OC 和 Cocoa 框架。既然遵循者类可以选择性的实现那些协议中的可选成员,那就说明,这些成员可能是实现了存在的,也可能是没有实现不存在的。因为 Swift 就将它们包装成了optional类型。
所以回到之前UIWindow的例子,window属性的类型是UIWindows??也就能解释了。因为window本身是UIWindow?类型,因为它是协议的可选成员,所以 Swift 又给它包装了一层,就变成了UIWindow??
给这样子的变量解包,需要解两次,即使用可选绑定需要两次。

继续查看UIApplicationDelegate这个协议的 API 声明,发现很多方法也都是可选的,调用这些方法的时候,Swift 也给他们包装了一下,所以也需要使用可选链式调用的方式。

借鉴 StackOverFlow 上一个例子,就很清楚了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@objc protocol MyApplicationDelegate {
optional var myWindow: MyWindow? {get set}
optional var doSomething()
}

class MyWindow: NSObject {
var rootViewController: Int?
init(number: Int) {
rootViewController = number
}
}

class MyAppDelegate: MyApplicationDelegate {
@objc var myWindow: MyWindow?
init() {
myWindow = MyWindow(number: 5)
}
}

class MyApplication: NSObject {
var myDelegate: MyApplicationDelegate
override init() {
myDelegate = MyAppDelegate()
}
}

let myApplication = MyApplication()

print(myApplication.myDelegate.myWindow??.rootViewController)

myApplication.myDelegate.doSomething?()

老实说,Swift 的这个可选类型真是蛋疼,一不小心就搞不清楚了。

参考

http://stackoverflow.com/questions/28901893/why-is-main-window-of-type-double-optional/28902036#28902036
http://www.apeth.com/swiftBook/ch04.html#SECoptionalProtocol
http://stackoverflow.com/questions/26201456/unwrap-uiwindow-twice-from-appdelegate-in-swift