Swift中的可选类型(Optional Type)

Optional Type总览

  • 什么是optional? Swift中声明的一个变量时, 默认情况下它是non-optional的, 即必须赋予这个变量一个非空的值. 如果给non-optional类型的变量赋值nil, 编译器就会报错.
var string1: String = "This is string1" // OK
string1 = nil  // Nil cannot be assigned to type 'String'
  • Swift中, 当声明一个类的属性时, 属性默认也是non-optional的
class MyClass { 
    var name: String = "Lv"
    var age: String  // Class 'MyClass' has no initializers
}
  • 对于之前使用Objective-C的同学来说, 这样的错误可能会让你们有些惊讶, 因为在Objective-C中, 当把nil赋值给一个变量或者声明一个没有初始值的属性时, 编译器都不会报错.
NSString *string1 = @"This is string1";
string1 = nil;

class MyClass { 
    NSString *name = @"Lv"
    NSString *age;
}
  • 然而并不意味着在Swift中不能声明一个没有初始值的属性. Swift中引入了可选类型(optional type)来解决这一问题. 它的定义是通过在类型生命后加加一个?操作符完成的.
class MyClass {
    var name: String?  // OK
    var age: String?  // OK
}

为什么引入可选类型?

  • Swift是一门安全的编程语言. 正如苹果所言, 可选类型就是证明Swift是一门安全的编程语言的一个小例子. 如上面的例子所示, Swift的可选类型提供了在编译阶段就检查一些可能在运行时才会出现的常见错误的机制. 下面通过下面的例子更好的诠释一下可选类型的威力.

  • 考虑下面一个Objective-C中的方法

- (NSString *)findStockCode:(NSString *)company {
    if ([company isEqualToString:@"Apple"]) {
        return @"AAPL";
    } else if ([company isEqualToString:@"Google"]) {
        return @"GOOG";
    }
    return nil;
}
  • 通过findStockCode:方法可以传入公司的名字,然后得到对应公司的股票代码. 为了方便演示, 这里只返回Apple和Google的代码, 对于其他的传入参数, 统统返回nil.
  • 假设在同一个类中调用findStockCode:方法:
NSString *stockCode = [self findStockCode:@"Facebook"]; // nil is returned
NSString *text = @"Stock Code - ";
NSString *message = [text stringByAppendingString:stockCode]; // runtime error
NSLog(@"%@", message);
  • 上面的代码是可以编译通过的, 但是因为传入了"Facebook"导致返回值是nil, 当代码开始运行后, 程序就会报错了.

  • 当使用Swift中的可选类型时, 上面在运行时才会出现的错误在编译阶段就会报错. 如果使用Swift的代码重写上面的例子, 代码如下:

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
print(message)
  • stockCode被定义成可选类型. 这就意味着它要么是一个String, 要么就是nil. 因为编译器在编译阶段就检测到了潜在的错误(可选类型的值没有被解包: value of optional type String? is not unwrapped)并且提示你去改正, 这段代码就不能够被执行.
  • 从这个例子中可以看到, Swift的可选类型加强了对空值的检查, 并且在编译阶段就给开发者提供了可能的错误信息. 很明显, 可选类型的引入可以让代码的质量变得更好.

解包可选类型(Unwrapping Optionals)

  • 如何让上面的代码正常运行? 很明显, 这里需要判断stockCode是否为空. 代码如下:
var stockCode:String? = findStockCode("Facebook")
let text = "Stock Code - "
if stockCode {
    let message = text + stockCode!
    print(message)
}
  • 像在Objective-C中一样, 还是使用if来判断stockCode中是否有值. 一旦确定stockCode中肯定有值时, 将一个感叹号(!)加在可选类型变量名后面来解包这个可选类型的变量. 在Swift中, 这叫做硬解包. 即直接在可选类型后面加一个感叹号来表示它肯定有值.
  • 上面的例子中, 我们只是自己知道stockCode肯定有值, 所以才直接硬解包了stockCode变量. 但是万一有时候我们的感觉是错的, 那程序在运行时可能会出现严重的错误. 所以Swift中是推荐先检查可选类型是否有值, 然后再进行解包的!
var stockCode:String? = findStockCode("Facebook")
let text = "Stock Code - "
let message = text + stockCode!  // runtime error
  • 以上代码在编译阶段不会报错.因为使用了硬解包, 编译器认为可选类型是有值的, 所以编译是通过的. 当代码运行起来时, 知名的错误将会出现: fatal error: Can’t unwrap Optional.None

可选绑定(Optional Binding)

  • 与硬解包不同, 可选绑定(Optional Binding)是一种更简单更推荐的方法来解包一个可选类型. 使用可选绑定来检查可选类型的变量有值还是没值. 如果有值, 解包它并且将值传递给一个常量或者变量.
  • 再多的言语描述也不如直接上代码来的实际! 将上面的代码改成可选绑定的代码如下:
var stockCode:String? = findStockCode("Facebook")
let text = "Stock Code - "
if let tempStockCode = stockCode {
    let message = text + tempStockCode
    print(message)
}
  • if let或者if var是可选绑定的两个关键字. 使用自然语言来描述上面这段代码的话, 意思就是如果stockCode有值,解包它,并且将它的值赋值给tempStockCode, 然后执行下面的条件语句; 如果stockCode为空, 直接跳过条件语句块.

  • 上面的代码可以简化如下:

let text = "Stock Code - "
if var stockCode = findStockCode("Apple") {
    let message = text + stockCode
    print(message)
}
  • 这里stockCode不再是可选类型, 所以不需要使用!来解包. 如果findStockCode:方法返回值是nil, 那么程序并不会执行条件语句块中的代码.

可选链式调用(Optional Chaining)

  • 在解释optional chaining之前, 先来修改一下上面的例子. 这里重新创建一个名为Stock的类, 包含两个可选类型的属性code和price. findStockCode:方法直接返回Stock类的对象, 而不再是String.
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
}
  • 改写了创建类的代码, 相应的调用代码也需要修改. 这里调用findStockCode:方法得到一个Stock对象, 然后计算买100股股票所需的价格.
if let stock = findStockCode("Apple") {
    if let sharePrice = stock.price {
        let totalCost = sharePrice * 100
        print(totalCost)
    }
}
  • 这里因为findStockCode:方法的返回值类型是可选类型, 所以使用if let进行可选绑定, 又因为price属性也是可选类型, 所以再使用一个if let来判断是否有值.
  • 上面的代码是一段正确的代码. 但是可以通过使用可选链式调用(Optional Chaining)来简化上面的代码, 毕竟上面是使用了两个if let的. 可选链式调用使我们能够通过?.链接多个可选类型. 示例代码如下:
if let sharePrice = findStockCode("Apple")?.price {
    let totalCost = sharePrice * 100
    print(totalCost)
}
  • 可选链式调用提供了另外一种方法获得price的值. 现在的代码看起来更加简洁. 这里只是对可选链式调用(Optional Chaining)做了简要介绍, 详细的介绍请阅读苹果官方文档.

Swift和Objective-C的互通性

  • Swift的可选类型是非常强大的. 尽管可能需要花一段时间熟悉它的语法.
  • Swift的设计是本着可以和Objective-C的API交互的原则. 当你想和UIKit或者其他框架的API交互时, 你肯定得需要可选类型. 下面是一些在实现table view时遇见的可选类型.
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
}

总结

  • 理解可选类型的工作原理是非常重要的. 在Swift中可选类型允许开发者能够在代码的编译阶段就发现一些潜在的问题, 从而确保在代码真正运行时能更安全. 一旦等你习惯了它的语法, 你必将会爱上可选类型这个新引入的概念.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容