swift 可选值简介

266 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第16天,点击查看活动详情

通过枚举解决魔法数问题

enum Optional<Wrapped> {
   some(Wrapped)
   none
}

获取枚举关联值的唯一方式就是通过模式匹配, 就像switch或if case let 中使用的匹配方法一样。和哨岗值不同,除非你式并检查解包,否则不能获取Optional中包装的值。

哨岗值:返回了一个“魔法”数来表示其并没有返回真实的值。这样的值被称为“哨岗值 (sentinel values)

extension Collection where Element: Equatable {
    func firstIndex(of element: Element) -> Optional<Index> {
        var idx = startIndex
        while idx != endIndex {
            if self[idx] == element {
                return .some(idx)
            }
            formIndex(after: &idx)
        }
        return .none
    }
}

返回的是Optional类型,非哨岗值

  • Optional可以写成Index?
  • 可选值遵循ExpressibleByNilLiteral协议,所以可以用nil表示 .none
  • return .some(idx) 就可以直接写为 return idx
  • 可选值就是一个普通的枚举值,完全可以自定义
let stringNumbers = ["1", "2", "three"]
let idx = stringNumbers.firstIndex(of: "1")
// 编译错误,remove(at:)接收Int,非Optional<Int>
stringNumbers.remove(at: idx)

必须解包

var stringNumbers = ["1", "2", "three"]
switch stringNumbers.firstIndex(of: "1") {
case .some(let idx):
    stringNumbers.remove(at: idx)
    break
case .none:
    break
}

更简明的写法:

  var stringNumbers = ["1", "2", "three"]
  switch stringNumbers.firstIndex(of: "1") {
  case let idx?:
      stringNumbers.remove(at: idx)
      break
  case nil:
      break
  }

可选值概览

if let
var array = ["one", "two", "three", "four"]
if let idx = array.firstIndex(of: "four") {
   array.remove(at: idx)
} 
if let idx = array.firstIndex(of: "four"), idx != array.startIndex {
   array.remove(at: idx)
}
let urlString = "https://www.objc.io/logo.png"
if let url = URL(string: urlString),
let data = try? Data(contentsOf: url),
let image = UIImage(data: data)
{
   let view = UIImageView(image: image)
   PlaygroundPage.current.liveView = view
}
if let url = URL(string: urlString), url.pathExtension == "png",
let data = try? Data(contentsOf: url),
let image = UIImage(data: data)
{
   let view = UIImageView(image: image)
}
while let

当一个条件返回nil时便终止循环

while let line = readLine() {
   print(line)
}
while let line = readLine(), !line.isEmpty {
   print(line)
}
  • next()遍历
let array = [1, 2, 3]
var iterator = array.makeIterator()
while let i = iterator.next() {
  print(i, terminator: " ")
} 
  • for..in + where
for i in 0..<10 where i % 2 == 0 {
print(i, terminator: " ")
} // 0 2 4 6 8

while+迭代器重写

var iterator2 = (0..<10).makeIterator()
while let i = iterator2.next() {
guard i % 2 == 0 else { continue }
  print(i)
}
双重可选值
let stringNumbers = ["1", "2", "three"]
let maybeInts = stringNumbers.map { Int($0) }
for maybeInt in maybeInts {
// maybeInt 是一个 Int? 值
// 得到两个整数值和一个 `nil`
}

for...in 是 while 循环加上一个迭代器的简写方式。由于 next 方法会把序列中的每个元素包装成可选Optional<Optional>值,或者说是一个Int??,而while let 会解包检查这个值是不是nil,如果不是,则绑定解包的值并运行循环体部分

var iterator = maybeInts.makeIterator()
while let maybeInt = iterator.next() {
    print(maybeInt, terminator: "")
}

当循环到"three"转换而来nil.从next()返回的是一个非nil的值,这个值是.some(nil).while let 将这个值解包,并将解包结果(也就是nil)绑定到maybeInt上。

如果只想对非nil的值做for循环,可以用case来做模式匹配

for case let i? in maybeInts {
    //  i 将是 Int 值,而不是 Int?”
    print(i, terminator: "")
}
// 或者只对 nil 值进行循环
for case nil in maybeInts {
    // 将对每个 nil 执行一次
    print("No value")
}”

x?这个模式只会匹配非nil值。这个是.some(x)简写

for case let .some(i) in maybeInts {
   print(i)
}

基于case的匹配模式可以让我们在switch的匹配中用到的规则应用到for, if和while上去.最有用的场景是结合可选值,但也有其它一些使用方式

let j = 5
if case 0..<10 = j {
    print("(j) 在范围内")
} // 5 在范围内”