美文网首页
Swift 可选型

Swift 可选型

作者: Alter桐 | 来源:发表于2020-06-12 14:51 被阅读0次

    处理缺失的数据

    我们已经会使用 Int 这样的类型来存储像 5 这样的数值。不过,当你想要存储用户年龄这样的属性,并且你还不知道该用户的年龄时你该怎么做呢?

    Swift的解决方案称为 optional,即可选型。你可以基于任意类型创建可选型。一个可选型整数可以有诸如1或者1000这样的数字,也可能没有任何值,即值可以缺失,Swift里表示缺失是用 nil。
    为了把一个类型变成可选型,只需要在类型后面加一个问号(?)。举个例子,我们可以这样把一个整数变成可选型:

    var age: Int? = nil
    

    这个可选型现在并不保有任何整数,但是如果之后我们知道了年龄值,我们可以给它重新赋值。

    age = 38
    

    解包可选型
    可选型字符串可能包含一个 “Hello” 或者 nil。
    考虑这样一个可选字符串:

    var name: String? = nil
    

    当我们调用 name.count 时会发生什么呢?一个真的字符串有 count 属性,它存储字符串的字符数量。但它是 nil 时,并没有 count 属性。
    因此,试图直接使用 name.count 是不安全的,Swift也不允许。取而代之的是,我们必须先检查可选型里面有些什么,这个过程被称为 解包 。
    解包可选型的一种常见做法是采用 if let 语法,它是一种有条件的解包。如果可选型里有值,你就可以使用它,如果没有则条件失败。
    举个例子:

    if let unwrapped = name {
      print("\(unwrapped.count) letters")
    } else {
      print("Missing name.")
    }
    

    如果 name 持有一个字符串,这个字符串会被放进一个叫 unwrapped 的常规字符串,然后我们就可以读取它的 count 属性。另一种情况,如果 name 是 nil, 那么 else 中的代码将被执行。

    用guard解包可选型
    if let 的一个替代方案是 guard let,后者也可以用来解包可选型。guard let 会为你解包,但是当它发现可选型里是 nil 时会期望你退出它所处的函数,循环或者条件。
    因此,if let 和 guard let 的主要区别在于 guard let 之后可选型还可以继续使用。
    让我们尝试一下 greet() 函数。它将接收一个可选字符串作为唯一的参数,当它解包发现这个参数是nil时会打印消息并且退出函数。因为可选型 unwrapped 在 guard let 的语句块结束之后作用,我们可以在函数最后打印这个解包后的字符串。

    func greet(_ name: String?) {
      guard let unwrapped = name else {
        print("You didn't provide a name!")
        return
      }
      print("Hello, \(unwrapped)!")
    }
    

    通过使用 guard let,你可以在函数的前面解决可选型的解包问题,在这之后的代码就可以愉快地使用解包后的值而不必担心出错了。

    强制解包
    可选型代表可能存在也可能不存在的数据,而某些时候你是 确切 知道一个可选型的值并不是nil。在这种情况下,Swift允许你强制解包这个可选型:通过把一个可选型转换为非可选型的方式。
    举个例子,下面你试图将一个字符串转换成 Int,就像这样:

    let str = "5"let num = Int(str)
    

    上面的代码中 num 其实是一个可选型 Int。因为你有可能尝试转换一个不是数字的字符串导致转换失败。
    尽管Swift不确定转换能否成功,你自己是知道你的结果是否可以安全地执行强制解包。这个强制解包是通过在Int(str) 之后添加 !来完成的。

    let num = Int(str)!
    

    通过强制解包,Swift将立刻解包 num,把它变成一个常规的 Int 而不是 Int?。但是假如你是错的,比方说 str是某个不能转换成数字的字符串,那么你的代码将崩溃。
    强制解包的操作符常常被戏称为崩溃操作符。因此,只有当你确信强制解包是安全的你才能这么做。

    隐式解包可选型
    像常规可选型一样,隐式解包可选型可能包含一个值也可能包含 nil。不过,跟常规可选型不一样的是,隐式解包可选型时不需要再解包,你可以把它们当做非可选型一样使用。
    隐式解包可选型是通过类型名之后添加感叹号来声明的,就像这样:

    let age: Int! = nil
    

    由于它们表现起来就像已经被解包过的样子,你并不需要 if let 或者 guard let 这样的代码来测试它们。不过,当你试图使用它们而它们实际上没有值时,即它们的值是 nil时,你的代码将崩溃。
    隐式解包可选型存在的意义在于,有些情况某个变量一开始时是nil,但当你需要用到它之前它总会有值。如果你可以确信这一点,那么采用隐式解包可选型可以省去一直写 if let 的麻烦。
    值得一提的是,如果条件允许采用常规可选型,安全起见最好总是采用常规可选型。

    空合运算符
    空合运算符解包一个可选型,如果可选型包含值则返回这个值,如果可选型不包含值,即可选型的值是 nil,那么返回某个默认值。两种情况,返回值都不再是可选型:它要么是可选型里包含的有效值,要么是一个备选的默认值。
    下面是一个接收整数作为唯一参数并且返回可选型字符串的函数:

    func username(for id: Int) -> String? {
      if id == 1 {
        return "Taylor Swift"
      } else {
        return nil
      }
    }
    

    如果我们以ID 15来调用这个函数,我们将得到 nil。通过空合运算符,我们可以提供一个叫 “Anonymous” 的默认值,就像这样:
    let user = username(for: 15) ?? "Anonymous"复制代码
    它将检查 username() 函数返回的值:如果是一个字符串,它将被解包并放入 user,如果是 nil,则使用 “Anonymous” 替代。

    可选链

    在使用可选型时,Swift为我们提供了一种快捷方式:假如你试图访问形
    如 a.b.c 这样的代码并且 b 是可选型,你可以在 b 后面写一个问号来启用 可选链: a.b?.c。
    当代码运行时,Swift会检查 b 是否有值,如果它是 nil,那么这行代码剩下的部分将被忽略。Swift会立即返回 nil。但是如果 b 有值,它将被解包,代码执行将继续。
    下面是一个名字的数组:

    let names = ["John", "Paul", "George", "Ringo"]
    

    我们将使用数组的 first 属性,如果数组里第一个名字有值的话返回这个名字,如果数组为空则返回 nil。在结果之上,我们调用 uppercased() 把它变成一个全大写的字符串:

    let beatle = names.first?.uppercased()
    

    上面的问号就是可选链。如果 first 返回 nil,那么Swift就不会尝试将它转换成全大写,它会将 beatle 立即设置为 nil。

    可选型 try

    看下面的代码:

    enum PasswordError: Error {
      case obvious
    }
    func checkPassword(_ password: String) throws -> Bool {
      if password == "password" {
        throw PasswordError.obvious
      }
      return true
    }
    do {
      try checkPassword("password")
      print("That password is good!")
    } catch {
      print("You can't use that password.")
    }
    

    通过 do, try 和 catch,我们得以运行可能抛出错误的函数,并且优雅地处理错误。
    上面的 try 写法其实有另外两种选择,这两种选项都能加深你对可选型和强制解包的理解。
    第一个是 try?,它将可能抛出错误的函数转换成返回可选型的函数。如果函数抛出错误,那你就会得到 nil作为函数的执行结果,否则你会得到将返回值包装之后的可选型。
    尝试使用 try? 来执行 checkPassword(),像下面这样:

    if let result = try? checkPassword("password") {
      print("Result was \(result)")
    } else {
      print("D'oh.")
    }
    

    另外一种选择是 try!,当你确信函数一定不会失败时你可以采用它。如果函数实际抛出了错误,你的代码将崩溃。
    使用 try! 来重写前面的代码:

    try! checkPassword("sekrit")print("OK!")
    

    失败构造器

    当我们说到强制解包的时候,我用了下面的代码:

    let str = "5"
    let num = Int(str)
    

    它将一个字符串转换成一个整数,但由于你可以传入任意字符串,你得到的实际上是一个可选型整数。
    这里用到一种叫做 失败构造器 的东西:它是一种可能成功也可能失败的构造器。你在结构体或者类里面用 init?() 来实现失败构造器。如果某些东西出错,它将返回 nil。因此这种构造器返回的是某种类型的可选型,你用之前需要解包。
    举个例子,我们现在要求 Person 结构体必须通过一个9字符的ID字符串来构造。只要不是9个字符,都会返回 nil。

    Swift 代码如下:

    struct Person {
      var id: String
    
      init?(id: String) {
        if id.count == 9 {
          self.id = id
        } else {
          return nil
        }
      }
    }
    

    类型转换

    Swift 知道每个变量的类型,但有的时候你知道的信息比Swift更多。举个例子,这里有三个类:

    class Animal { }class Fish: Animal { }class Dog: Animal {
      func makeNoise() {
        print("Woof!")
      }
    }
    

    我们创建几个Fish和几个Dog,然后把它们放进一个数组,像下面这样:

    let pets = [Fish(), Dog(), Fish(), Dog()]
    

    Swift 知道 Fish 和 Dog 都继承自 Animal 类,因此它通过类型推断将 pets 创建为一个 Animal 类型的数组。
    如果我们想遍历 pets 数组,让所有的狗发出叫声,我们需要执行一次类型转换:Swift将检查每个pet是否 Dog对象,以便我们调用 makeNoise() 方法。
    这里用到了一个关键字 as?,它将返回一个可选型:类型转换失败时返回 nil,成功则返回转换后的类型。
    Swift代码如下:

    for pet in pets {
      if let dog = pet as? Dog {
        dog.makeNoise()
      }
    }
    

    总结

    让我们来总结一下。

    1.可选型让我们可以用一种清晰无歧义的方式表示值缺失的情况。
    2.Swift不允许不经解包就使用可选型,解包可以用 if let 或者 guard let。
    3.你可以用感叹号强制解包可选型,不过如果解出来的是 nil ,你的代码将会崩溃。
    4.隐式解包可选型没有做常规可选型的安全性检查。
    5.你可以使用空合运算符解包一个可选型,以便可选型里没有值时提供一个默认值。
    6.可选链用于操作可选型,如果可选型的值是空的,后续代码将被忽略。
    7.你可以使用 try? 将一个可能抛出错误的函数转换成一个可选型的返回值,或者使用 try! 在抛出错误时崩溃。
    8.如果你需要在输入不合理时让构造过程失败,可以使用 init?() 来创建失败构造器。
    9.你可以使用类型转换将一个类型转换为另一个类型。

    感谢@猫克杯 https://juejin.im/post/5e1543275188253a6a20ea24

    相关文章

      网友评论

          本文标题:Swift 可选型

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