SwiftiOSSwift 学习

Swift的安全保障 - Optional特性

2016-07-23  本文已影响462人  flionel
Optional.png

Optionals Overview

Swift语言一个很大的特色和难点就是Optional可选值的概念,这也是Swift相对于Objective-C语言来说一个很大的改进和安全性保障。这里先通过简单的optional和non-optional类型变量定义,让读者有一个初步的认识,如下代码所示,

// non-optional类型
var helloworld1: String = "hello world 1"

// Optional类型
var helloworld2: String? = "hello world 2"
var helloworld3: String? 

通常情况下,在Swift中定义属性,它们默认是non-optional(非Optional类型)。定义为non-optional类型的变量必须在定义的时候或在-init方法中初始化,如果non-optional没有赋值初始化,则会发生编译错误,这是non-optoinal会出现的第一种编译错误①,例如下面的语句,helloworld1定义之后赋值初始化,而helloworld2定义之后没有赋值初始化,就会发生编译错误,

class Messnger {
    var helloworld1: String = "Swift is awesome!" // OK
    var helloworld2: String // compile-time error
}

回顾Objecive-C语言,我们可能经常定义一些属性或变量,在使用之前却忘了进行赋值,这可能会导致一些莫名其妙的bug,甚至使应用程序Crash;为了解决这样的问题,保证程序安全性,Swift要求non-optional类型必须保证完整地初始化,其实这是从语法上对开发者进行了规范。所以上述代码中的non-optional类型的helloworld2变量,应该跟helloworld1一样,赋值以明确的值。

既然必须对non-optional变量赋值,是否可以直接赋值为nil呢?答案是NO,因为non-optional不能赋值为nil,只有Optional类型的变量才能赋值为nil,例如下面的代码

// non-optional
var helloworld1 = nil // compile-time error

// Optional类型
var helloworld2: String? = nil // OK
var helloworld3: String? = nil // OK

如果将nil赋值给non-optional类型,编译器会提示说“不能为non-optional设置为nil值”,这是non-optoinal会出现的第二种编译错误②,只有Optional类型的变量才能被设置为nil,对于Objective-C来说,就不会出现以上两种编译错误,即

使用?关键字来定义Optional变量可以解决上面的问题,类似文章开头的范例,请看下面的变量定义,

class Messenger {
    var helloworld1: String = "Swift is awesome" // OK
    var helloworld2: String? // OK
}

此处helloworld1是默认non-optional类型,对于non-optional类型的变量必须在定义的时候或者在-init方法中明确的指定初始值,否则编译器会提示编译错误;helloworld2是Optional,Optional类型变量helloworld2在定义的时候可以不指定初始值,而且也可以将nil赋值给Optional的变量。

Why Optionals

Swift为什么要使用Optional这样的特性呢,在学习Swift和编码的实践中,读者会意识到苹果做这样的设计更多的是为了程序安全考量,Swift一开始就被设计为一种更加安全的编程语言。读者通过上面的例子可以看出来,Swift的Optional特性提供了编译时检测(compile-time check),这样可以避免很多发生在运行时的错误(run-time error)。应用程序的生命周期就是“编码 - 调试 - Debug -运行”,以往在编写Objective-C代码时候,因为Objective-C语言并没有很严格的安全检查,所以可能会导致bug或者Crash发生在运行阶段,特别是发布到App Store的程序出现了这样的问题会很棘手,也就是说Objective-C将编码失误的隐患留到了运行时候;反观Swift,它通过比较严格的安全检查,在应用程序声明周期的编码部分就开始排查隐患,这样的好处是显而易见的。

接下通过一个范例,读者会对Swift的Optional特性的强大有更好的了解,我以一个简单的股票模型来举例说明,

先展示Objective-C代码,

- (NSString *)findStockCode:(NSString *)company {
    if ([company isEqualToString:@"Apple"]) {
        return @"APPL"
    } else if ([company isEqualToString@"Google"]) {
        return @"GOOG";
    }
    return nil;
}

/*
    方法-findStockCode用来获取公司的股票码,
    在这个范例中,该方法仅仅返回Apple和Google的股票码,
    对于其他的输入,则返回nil。
*/

// Usage of -findStockCode method
NSString *stockCode = [self findStockCode:@"Facebook"];
NSString *text = @"Stock Code - ";
NSString *message = [text stringByAppendingString:stockCode]; // runtime
NSLog(@"%@", message)

当我们输入公司名为Fackbook时,-findStockMethod在编译时候没有什么问题,但是在运行时,该方法返回nil,导致了-stringByAppendingString方法Crash。

但是我们使用Swift Optional特性时候,相对于Objective-C,它不是在运行时(run time)才发现错误,而是在编译时(compile time)就检测出语法错误,从而排除了安全隐患,

用Swift重写-getStockCode,代码如下所示,

func findStockCode(company: String) -> String? {
    if company == "Apple" {
        return "APPL"
    } else if company == "Google" {
        return "GOOG"
    }
    
    return nil
}

// Usage of -findStockCode method
var stockCode: String? = findStockCode("Facebook")
let text = "Stock Code -"
let message = text + stockCode // compile-time error
print(message)

-findStockCode方法返回值定义为String?,它表示既有可能返回为空,也有可能不为空,这是可选的,所以定义字符串stockCode也是String?类型,此时编译器已经检测到潜在的错误 - "value of optional type String? is not unwrapped",开发者必须解决上面的问题才能编译。

通过上面的范例我们了解到,Swift的Optional特性强制的进行nil-check(空数据检查),并且通过编译错误提示开发者代码存在潜在的问题,so,多多使用Optional可以保证更高的代码质量。

Unwrapping Optionals

上面的代码出现了编译错误,怎样更改让代码编译通过呢?按照Objective-C的编程经验,我们会判断stockCode字符串是否为空,将代码修改如下,

var stockCode: String? = findStockCode("Facebook")
let text = "Stock Code - "
if stockCode { // detect stockCode if contains value
    let message = text + stockCode!
    pritn(message)
}

我们对此做了改进,使用if来判断Optional类型的stockCode是nil还是包含有效值,如果stockCode不为空,则使用!来对其进行强制解包。

使用Optional值之前需要对其解包,为什么要解包呢?

因为Optional本身也是一种枚举值,它包含两种值和Optional.None和Optional.Some,所谓的nil就是Optional.None,非nil就是Optional.Some,定义为Optional的变量或常量,首先会通过Some(T)包裹Wrap其原始值,也就是说它们的真实内容被Optional包裹起来,所以使用的时候需要对Optional进行解包Unwrap

上面我们使用stockCode!来进行强制解包可选值,并且使用被可选值包裹(Wrap)的值。

需要强调的是若对Optional使用!强制解包,必须保证该Optional包含一个不为空的值(a non-nil value),否则就会导致Crash。上面的stockCode!,在编译时仅仅进行了nil-check之后我们就对其进行强制解包,在运行时,这直接导致了Crash,

var stockCode: String?  = findStockCode("Facebook") // return nil
let text = "Stock Code - "
let message = text + stockCode! // runtime error
/*
    此处没有编译错误,编译器认为Optional值stockCode确定包含有效值,
    然而在运行时Crash,Xcode会提示"fatal error: Can't unwrap Optional.Nome"
*/

Optional Binding

因为使用!进行强制解包可能会导致Crash,Apple提供了更加安全和简单的解包Optional方式Optional Binding(可选绑定),使用Optional Binding来检测一个Optional是否包含有效值,如果该Optional变量确实包含有效值,则将其解包,并且将解包的值赋值给一个临时的常量或变量,使用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,使用Optional Binding,翻译过来就是“如果stockCode包含有效值,则对其解包,将解包的值复制给临时常量tempStockCode,然后运行条件语句中的代码;否则直接跳过条件语句的代码块”,这样就避免了使用!进行强制解包导致的Crash。

Optional Chaining

在讨论Optional Binding(可选绑定)之前,对上面的范例代码做稍微的改造,首先创建一个名为Stock的类,它拥有有个Optional类型的属性,名为code(股票码)和price(价格);然后修改-findStockCode方法,将返回值由String改为Stock,如下所示,

/*
    输入公司名称,返回该公司股票信息Stock,
    Stock包括股票码和价格
*/
class Stock {
    var code: String?
    var price: Double?
}

func findStockCode(company: String) -> Stock? {
    if company == "Apple" {
        let appl: Stock = Stock()
        appl.code = "APPL"
        appl.price = 90.32
        return appl
    } else if company == "Google" {
        let goog: Stock = Stock()
        goog.code = "GOOG"
        goog.price = 556.36
        return goog
    }
    return nil
}

如果想购买100股Apple的股票,可以这样编写代码,

// Usage -findStockCode with param "Apple"
if let stock = findStockCode("Apple") {
    if let singlePrice = stock.price {
        // 购买100股Apple股票
        let totalCost = singlePrice * 100
        print(totalCost)
    }
}

为了计算“购买”100股Apple的股票所需Money,首先对-findStockCode的返回值Stock?进行解包,接着对Stock?的price?可选值Optional属性进行解包,这样写没有什么问题,这样过多的嵌套if导致了代码的复杂度增加,可读性降低。

这时候Optional Chaining可以帮助简化代码,Optional Chaining可选绑定特性允许我们通过?.操作符来连接多个Optional属性,代码稍作优化,如下所示,

if let singlePrice = findStockCode("Apple")?.price {
    let totalCost = singlePrice * 100
    print(totalCost)
}

Optional Chaining提供了更为灵活的方式去获取price的值,这样优化后的代码更加简洁更加清晰。

以上,是对Swift的Optional特性做的一个简单介绍和范例演示,有些概念或理论说的可能不正确,欢迎强烈吐槽。如果读者想深入了解和研究,可以在Google搜索更多资源,或者阅读苹果的官方文档Apple's Swift guide

参考链接

公众号

欢迎关注本人公众号 foolishlion,请扫描下方二维码。

foolishlion.jpg
上一篇下一篇

猜你喜欢

热点阅读