美文网首页
swift 可选类型

swift 可选类型

作者: _莫忘初心 | 来源:发表于2020-09-17 20:45 被阅读0次

可选类型:可以用来表示可能缺失或者计算失败的值


举例:

let cities = ["Paris": 2241, "Madrid": 3165, "Amsterdam": 827, "Berlin": 3562]

let madridPopulation: Int? = cities["Madrid"]

madridPopulation 的类型是可选类型 Int?,⽽⾮ Int。⼀个 Int? 类型的值是 Int 或者特

殊的 “缺失” 值 nil。


当我们想要使用madridPopulation的时候,你也许会这样写:

if madridPopulation != nil {

print ("The population of Madrid is \(madridPopulation! * 1000)") 

} else {

print ("Unknown city: Madrid") 

}

又或许会这样写:

if let madridPopulation = cities["Madrid"] {

print ("The population of Madrid is \(madridPopulation * 1000)") 

} else {

print ("Unknown city: Madrid")

}

当我们采用第一种方式的写法madridPopulation! ,!代表强制转换,将可选Int类型转为Int类型,又叫做强制解包。

第二种方式又叫做可选绑定机制,可以将madridPopulation定义与检验相结合,这样就不需要显式地使⽤强制解包运算符!


如果可以选择,强烈推荐使用可选绑定机制而非强制解包。每当你一不留神用到!的同时,可能有一只小猫就会死。

如果你有⼀个 nil 值,强制解包可能导致崩溃;可选绑定⿎励你显式地处理异常情况,从⽽避免运⾏时错误。


Swift 还给 ! 运算符提供了⼀个更安全的替代,?? 运算符。使⽤这个运算符时,需要额外提供

⼀个默认值,当运算符被运⽤于 nil 时,这个默认值将被作为返回值。

需要顺便说一句的是,当我们使用 optional ?? defaultValue 时,swift 标准库为我们做了哪些事?

如果 default 的值是通过某个函数或者表达式得到的,那么⽆论可选值是否为 nil,defaultValue 都会被求值。?? 运算符应该只在可选值参数是 nil 时才对 defaultValue 参数进⾏求值。而我们可以通过显示闭包来达到目的 optional ?? {defaultValue}

Swift 标准库中的定义通过使⽤ Swift 的 @autoclosure 类型标签来避开创建显式闭包的需求。它会在所需要的闭包中隐式地将参数封装到 ?? 运算符。这样⼀来,用户⽆需再显式地创建闭包封装 defaultValue 参数。


那再思考一个问题:当多个可选值组合再一起的时候,写起来是不是很麻烦?

swift 有一个特殊的机制,叫可选链。

它用于再被嵌套的类或者结构体中,对方法或属性进行选择。

下面,我想拿到客户的地址

struct Order {

let orderNumber: Int

let person: Person?

}

struct Person {

let name: String

let address: Address?

}

我们可以使用显式解包运算符:order.person!.address! ,上面说过,如果任意中间数据缺失,可能会导致运行时异常。所以使用可选绑定更安全。

if let myPerson = order.person {

if let myAddress = myPerson.address {

// ...

if let myPerson = order.person ,let myAddress = myPerson.address {// ...

是不是很繁琐,或者你允许这么长的话。

使用可选链的话:使用问号运算符来尝试对可选类型进行解包,而不是强制解包。当任意一组成项失败时,整条语句将返回nil

if let myState = order.person?.address{

} else {

}

上面也可以利用guard 语句尽早退出作用于

guard let let myState = order.person?.address  else { return }


为什么要用可选值

swift的类型系统相当严格,一旦有可选类型,就必须处理可能为nil的问题。

在许多语⾔中并没有可选,空指针是危险的来源。Objective-C 稍好⼀些,你可以安全地向 nil 发送消息,然后根据不同的返回值的类型,得到像是 nil、数字 0 这样的零值。

为什么在 Swift 中要改变这种特性呢???

选择显式的可选类型更符合 Swift 增强静态安全的特性。强⼤的类型系统能在代码执⾏前捕获到错误,⽽且显式可选类型有助于避免由缺失值导致的意外崩溃。

Objective-C 采⽤的默认近似零的做法有其弊端,可能你会想要区分失败 (键不存在于字典) 和成功返回 nil (键存在于字典,但关联值是 nil) 两种情况。若要在 Objective-C 中做到这⼀点,你只能使⽤ NSNull。

虽然在 Objective-C 中将消息发送给 nil 是安全的,但是使⽤它们往往并不安全。

⽐⽅说我们想创建⼀个属性字符串 (attributed string)。如果我们传递 nil 作为 country 的值,那么 capital 也会是 nil,但是当我们试图将 NSAttributedString 初始化为 nil 时,将会引起崩溃:

- (NSAttributedString *)attributedCapital:(NSString *)country{

NSString *capital = self.capitals[country];

NSDictionary *attr = @{ /* ... */ };return [[ NSAttributedString alloc] initWithString:capital attributes:attr ];

}

虽然像上⾯那样的崩溃并不经常发⽣,不过⼏乎每个开发者都写过像这样导致崩溃的代码。⼤多数时候,在调试阶段这些这些崩溃的导⽕索就会被发现,不过偶尔难免不知不觉就发布了代码,⼀些情况下,变量还可能出乎意料的是 nil。因此,许多程序员使⽤断⾔来显式地标⽰这这种情况。例如,我们可以添加⼀个 NSParameterAssert 以确保当 country 是 nil 时就⽴即崩溃:

- (NSAttributedString *)attributedCapital:(NSString *)country {

NSParameterAssert(country);

NSString *capital = self.capitals[country];

NSDictionary *attr = @{ /* ... */ };

return [[ NSAttributedString alloc] initWithString:capital attributes:attr ];

}

如果我们传递了⼀个 country 值,但是 self.capitals 中并没有键与之匹配该怎么办呢?这种情况发⽣的概率很⼤,特别是当 country 来源于⽤⼾输⼊时。在这种情况下,capital 将会是nil,我们的代码仍然会崩溃。当然,修复的⽅法很简单。不过,我们关注的重点是,在 Swift 中使⽤nil 编写健壮的代码⽐在 Objective-C 中容易。

最后,从本质上看,使⽤断⾔是⾮模块化的。假设我们要实现⼀个 checkCountry ⽅法⽤于确认是否⽀持⾮空 NSString *。可以很容易地在上述代码中加⼊该⽅法:

- (NSAttributedString *)attributedCapital:(NSString*)country {

NSParameterAssert(country);

if (checkCountry(country)) {

// ...

}

现在的问题是:checkCountry 函数是否也应该断⾔它的参数不是 nil?⼀⽅⾯,它不应该 因为我们⽅才刚在 attributedCapital ⽅法中执⾏了检验的代码。另⼀⽅⾯,如果checkCountry 函数仅适⽤于⾮ nil 值,我们还是应该复制上述断⾔。我们被迫在暴露不安全接⼝和复制断⾔之间进⾏选择。还有⼀种做法是,可以给签名添加⼀个 nonnull 标注,当该⽅法被⼀个可能为 nil 的值调⽤时,它会发出警告,但这种做法在⼤多数 Objective-C 的代码库中并不常⻅。在 Swift 中,事情要来得容易得多:函数签名可以显式地使⽤可选值来提⽰⼀个值可能为 nil。与他⼈协同编码时,这是⼗分宝贵的信息。下⾯的签名提供了⼤量信息:

func attributedCapital(country: String) -> NSAttributedString?

我们不仅被警告有失败的可能性,还知道了必须传递⼀个 String 作为参数 —— 且不能是 nil值。像上⾯⼀样的崩溃将不会再发⽣。此外,这也是编译器要检验的信息。⽂档很容易过时,但是你可以永远依赖函数签名。

在 Objective-C 中处理标量值时,可选问题显得更加棘⼿。不妨看看下⾯的⽰例,尝试在⼀个字符串中查找⼀个特定关键词的位置:

NSString *someString = ...;

if ([ someString rangeOfString:@"swift"].location != NSNotFound) {

NSLog(@"Someone mentioned swift!");

}

看起来毫⽆问题:如果 rangeOfString: 没有找到字符串,location 就会被设置为NSNotFound。NSNotFound 被定义为 NSIntegerMax。这段代码⼏乎是正确的,第⼀眼很难看到它的问题:若 someString 是 nil,rangeOfString: 将返回⼀个属性全为零的结构体,location 将返回 0。接着所做的判断结果若为真,if 语句中的代码将被执⾏。

如果有可选值的话,这⼀切将免于发⽣。如果我们想将这份代码转为 Swift,会需要做出⼀些结构性的改变。上⾯的代码会被编译器拒绝,类型系统也不会允许你在⼀个 nil 值上运⾏rangeOfString:。因此,⾸先需要将它解包:

if let someString = ... {

if someString.rangeOfString("swift").location != NSNotFound {

print ("Found") 

}

类型系统将有助于你捕捉难以察觉的细微错误。其中⼀些错误很容易在开发过程中被发现,但是其余的可能会⼀直留存到⽣产代码中去。坚持使⽤可选值能够从根本上杜绝这类错误。

相关文章

网友评论

      本文标题:swift 可选类型

      本文链接:https://www.haomeiwen.com/subject/ceolyktx.html