Swift 初学手册:可选类型 (Optionals)

在 Swift 中,当我们声明一个变量的时候,默认情况下是 非可选类型 (non-optional) ,也就是说,你必须指定一个不为 nil 的值。如果你硬是要把一个非可选类型的变量设为 nil ,那么编译器就会告诉你:“嘿你不能把它设置成 nil 好嘛”。没错就是这样:

<code>var message: String = Swift is awesome! // OK
message = nil // compile-time error
</code>

当然编译器给出的错误消息可就没这么友善了,一般会显示 Could not find an overload for ‘__conversion’ that accepts the supplied arguments 这种错误。同样,在类中声明的变量也是这样,默认情况下是费可选类型的:

<code>class Messenger {
    var message1: String = Swift is awesome! // OK
    var message2: String // compile-time error
}
</code>

message2 处你会得到一个编译错误,因为它没有初始值。对于那些从 Objective-C 一路走来的小伙伴们可能会感觉很意外,在 Objective-C 里这种情况根本就不会有问题好嘛:

<code>NSString *message = @Objective-C will never die!;
message = nil;
 
class Messenger {
    NSString *message1 = @Objective will never die!;
    NSString *message2;
}
</code>

不过,你也可以在 Swift 中声明一个没有初始化的变量, Swift 提供了可选类型来表示没有值的情况。只需要在声明的类型后面加上问号 ? 即可:

<code>class Messenger {
    var message1: String = Swift is awesome! // OK
    var message2: String? // OK
}
</code>

你也可以给可选类型的变量们赋值,如果不赋值那么它的值自动就设为 nil 。

缘由

为什么要这么设计呢?苹果官方给出的解释是,因为 Swift 是一门类型安全的语言。从前面的例子中可以看出, Swift的可选类型会进行编译检查,防止一些常见的运行时错误。让我们看一看下面的例子,这样可以更好地理解。

比如说,在 Objective-C 中有如下代码:

<code>- (NSString *)findStockCode:(NSString *)company {
    if ([company isEqualToString:@Apple]) {
        return @AAPL;
    } else if ([company isEqualToString:@Google]) {
        return @GOOG;
    }
 
    return nil;
}
</code>

在上面的方法里,你可以用 findStockCode 方法来获取到股票的代码,显然只有 Apple 和 Google 的查询会返回值,其他情况都会返回 nil 。

假设我们在下面的代码中会调用这个方法:

<code>NSString *stockCode = [self findStockCode:@Facebook]; // nil is returned
NSString *text = @Stock Code - ;
NSString *message = [text stringByAppendingString:stockCode]; // runtime error
NSLog(@%@, message);
</code>

这段代码在编译时不会有任何问题,但是如果输入的是 Facbook 则会返回 nil ,导致运行时错误。

而在 Swift 里,和运行时错误不用, Swift 会在编译时就提示错误信息,我们可以把上面的代码在 Swift 中重写:

<code>func findStockCode(company: String) -> String? {
   if (company == Apple) {
      return AAPL
   } else if (company == Google) {
      return GOOG
   }
 
   return nil
}
 
var stockCode:String? = findStockCode(Facebook)
let text = Stock Code - 
let message = text + stockCode  // compile-time error
println(message)
</code>

在上面的代码里, stockCode 被定义成了可选类型,这意味着它可以有一个 string 的值,也可以为 nil 。代码无法通过编译,会提示一个错误:value of optional type String? is not unwrapped

正如你在例子中看到的,Swift 的可选类型加强了 nil 检测,为开发者提供了编译时的检查,合理的使用可选类型可以有效地提高代码质量。

强制解析

慢着慢着,前面说了那么多好处,但是代码还是没通过编译啊!别急,我们需要检测一下 stockCode 是否为 nil ,把代码做如下修改:

<code>var stockCode:String? = findStockCode(Facebook)
let text = Stock Code - 
if stockCode {
    let message = text + stockCode!
    println(message)
}
</code>

和 Objective-C 中类似,我们先对它进行检测,看看它是不是有值。如果不为 nil ,我们可以在后面加上一个感叹号 ! 进行解析,这样 Xcode 就知道:“嗯我可以使用这个值了”。在 Swift 里我们称之为 强制解析 (forced unwrapping) ,通过感叹号强制获取可选类型的真实值。

再回到上面的代码中。我们只是在强制解析之前,检测了一下看看变量是否为 nil 而已。这和 Objective-C 中常见的 nil 检测也没啥区别啊。如果我忘了检测呢?看下下面的代码:

<code>var stockCode:String? = findStockCode(Facebook)
let text = Stock Code - 
let message = text + stockCode!  // runtime error
</code>

这样我们不会得到编译错误,因为我们用了强制解析,编译器已经假定这个可选类型肯定有值。显然这样是错误的,运行的时候会得到如下错误:
<code>fatal error: Can’t unwrap Optional.None
</code>

可选绑定

除了强制解析,可选绑定 (optional binding) 是一个更值得推荐的解析方案。 你可以用可选绑定来检测一个可选类型的值有没有值,如果有值则解析出来并存储到一个临时的变量里。

废话少说,放码过来!让我们来看看下面这个使用了可选绑定的示例代码:

<code>var stockCode:String? = findStockCode(Facebook)
let text = Stock Code - 
if let tempStockCode = stockCode {
    let message = text + tempStockCode
    println(message)
}
</code>

代码中的 if let (或者 if var ) 是可选绑定的两个关键词。翻译成人类语言,大概是这个样子:“如果 stackCode 它有值,把它的值存到 tempStackCode 里,然后继续执行接下来的代码块。如果它没值,跳过后面的代码块。” 因为tempStockCode 是一个新的常量,所以你不再需要添加 ! 后缀。

你可以把方法调用放在 if 里,这样代码看起来更简洁:

<code>let text = Stock Code - 
if var stockCode = findStockCode(Apple) {
    let message = text + stockCode
    println(message)
}
</code>

这里, stockCode 不再是可选类型,我们可以直接使用。如果 findStockCode 方法返回了 nil 则会跳过后面的代码块。

可选链

在解释可选链之前,我们先对原始代码做一些小小的修改。我们创建一个新的类叫做 Stock ,它有 codeprice 两个可选类型的属性。findStockCode 函数用来返回一个 Stock 对象,而不是一个 String 对象:

<code>class Stock {
    var code: String? 
    var price: Double? 
}
 
func findStockCode(company: String) -> Stock? {
    if (company == Apple) {
        let aapl: Stock = Stock()
        aapl.code = AAPL
        aapl.price = 90.32
 
        return aapl
 
    } else if (company == Google) {
        let goog: Stock = Stock()
        goog.code = GOOG
        goog.price = 556.36
 
        return goog
    }
 
    return nil
}
</code>

接下来,我们先用 findStockCode 函数查找股票的代码,然后计算购买100股所需要的总价:

code>if let stock = findStockCode(Apple) {
    if let sharePrice = stock.price {
        let totalCost = sharePrice * 100
        println(totalCost)
    }
}
</code>

函数的返回值是可选类型,我们通过可选绑定来检测是否有值,显然股票的价格也是一个可选类型,于是我们继续使用if let 来检测它是否有值。

上面的代码没有任何问题,不过这一层一层的 if 嵌套实在是太麻烦了,如果可选类型层次多点,很可能形成下面的情况:

<code>if let x = xxx() {
    if let x = xxx() {
        if let x = xxx() {
            if let x = xxx() {
                if let x = xxx() {
                    if let x = xxx() {
                        if let x = xxx() {
                            if let x = xxx() {
                                if let x = xxx() {
                                    if let x = xxx() {
                                        if let x = xxx() {
                                            if let x = xxx() {
 
                                            }        
                                        }
                                    }
                                }
                            }   
                        }
                    }        
                }
            }
        }
    }   
}
</code>

除了使用 if let ,我们可以通过可选链来简化代码。我们可以用问号将多个可选类型串联起来:

<code>if let sharePrice = findStockCode(Apple)?.price {
    let totalCost = sharePrice * 100
    println(totalCost)
}
</code>

可选链提供了访问变量的另一种方式,代码现在看上去也更加的干净整洁。上面只是一个基础的使用,更加深入的学习可以参考官方文档。

Swift 和 Objective-C 的交互

Swift 中的可选类型十分强大,尽管可能一开始的时候需要花点时间慢慢熟悉。可选类型让你的代码更清晰,而且可以避免一些 nil 引起的问题。

Swift 有设计与 Objective-C 交互的 API,当我们需要和 UIKit 或者其他框架的 API 交互的时候,你肯定会遇到可选类型。下面列举一些 UITableView 中可能会遇到的可选类型:

<code>func numberOfSectionsInTableView(tableView: UITableView?) -> Int {
    // Return the number of sections.
    return 1
}
 
func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
    // Return the number of rows in the section.
    return recipes.count
}
 
 
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
    let cell = tableView.dequeueReusableCellWithIdentifier(Cell, forIndexPath: indexPath) as UITableViewCell
 
    cell.textLabel.text = recipes[indexPath.row]
 
    return cell
}
</code>

总结

对于一名开发者来说,理解可选类型的工作原理是十分必要的。这也就是为什么我们专门写一篇文章来介绍可选类型。它可以帮助开发者在编译阶段就发现隐藏的问题,从而避免运行时错误。当你习惯了这种语法,你将会愈发欣赏可选类型的魅力所在。

享受这个美好的世界吧。真棒。
数字信号处理理论、算法与实现》是2003年清华大学出版社出版的图书,作者是胡广书。绪论 O.1数字信号处理的理论 O.2数字信号处理的实现 0.3数字信号处理的应用 O.4关于数字信号处理的学习 参考文献 上篇经典数字信号处理 第1章离散时间信号与离散时间系统 1.1离散时间信号的基本概念 1.1.1离散信号概述 1.1.2典型离散信号 1.1.3离散信号的运算 1.1.4关于离散正弦信号的周期 1.2信号的分类 1.3噪声 1.4信号空间的基本概念 1.5离散时间系统的基本概念 1.6LSI系统的输入输出关系 1.7LSI系统的频率响应 1.8确定性信号的相关函数 1.8.1相关函数的定义 1.8.2相关函数和线性卷积的关系 1.8.3相关函数的性质 1.8.4相关函数的应用 1.9关于MATLAB 1.10与本章内容有关的MATLAB文件 小结 习题与上机练习 参考文献 第2章Z变换及离散时间系统分析 2.1Z变换的定义 2.2Z变换的收敛域 2.3Z变换的性质 2.4逆Z变换 2.4.1幂级数法 2.4.2部分分式法 2.4.3留数法 2.5LSI系统的转移函数 2.5.1转移函数的定义 2.5.2离散系统的极零分析 2.5.3滤波的基本概念 2.6IIR系统的信号流图与结构 2.6.1IIR系统的信号流图 2.6.2IIR系统的直接实现 2.6.3IIR系统的级联实现 2.6.4IIR系统的并联实现 2.7用z变换求解差分方程 2.8与本章内容有关的MATLAB文件 小结 习题与上机练习 参考文献 第3章信号的傅里叶变换 3.1连续时间信号的傅里叶变换 3.1.1连续周期信号的傅里叶级数 3.1.2连续非周期信号的傅里叶变换 3.1.3傅里叶级数和傅里叶变换的区别与联系 …… 下篇统计数字信号处理 附录 索引
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值