阅读 792

木又的《Swift进阶》读书笔记——可选值

可选值

哨岗值

  • C代码

    int ch;
    while ((ch = getchar()) != EOF) {
    	printf("Read character %c\n", ch);
    } 
    printf("Reached end-of-file\n");
    复制代码
  • C++ 代码

    auto vec = {1,2,3};
    auto iterator = std::find(vec.begin(),vec.end(),someValue);
    if (iterator != vec.end()) {
      std::cout << "vec contains" << *iterator << std:endl;
    }
    复制代码
  • Java代码

    int i = Integer.getInteger("123")
    复制代码
  • Objective-C

    [[NSString alloc] initWithContentsOfURL: url encodig: NSUTF8StringEncoding error: &error];
    复制代码

在上面的所有例子中,这些函数都返回了一个“魔法”数来表示并没有返回真实的值。这样的值被称为“哨岗值 (sentinel values)”

通过枚举解决魔法数的问题

  • 大多数语言都支持某种形式的枚举类型,用它表达某个类型可能包含的所有值是一种更为安全的做法。Optional 也是通过枚举实现的

    enum Optional<Wrapped> {
      case none
      case some(wrapped)
    }
    复制代码

可选值概览

  • 可选值在 Swift 中有很多来自语言内建的支持

if let

  • **if let ** 语句会检查可选值是否为 nil,如果不是nil,便会解包可选值。

while let

  • while let 语句和 if let 非常相似,它表示当一个条件返回 nil 时便终止循环。

双重可选值

  • 一个可选值的包装类型是也是一个可选值的情况,这会导致可选值的嵌套。

  • 基于 case 的模式匹配可以让我们把在 switch 的匹配中用到的规则同样地用到 if,for 和 while上去。最有用的场景是结合可选值。

if var and while var

  • 除了 let 以外,你还可以使用 var 来搭配 if、while 和 for。这让你可以在语句块中改变变量:

    let number = "1"
    if var i = Int(number) {
      i += 1
      print(i)
    } // 2
    复制代码

解包后可选值的作用域

  • guard let

  • Never 又被叫做无人类型 (uninhabited type)。这种类型没有有效值,因此也不能够被构建。在泛型环境里,把 NeverResult 结合在一起是非常有用的。例如,对于一个接受Result<A, E> (A 和 E 都是泛型参数) 的泛型 API,你可以传入一个 Result<..., Never> 表示这个结果中永远都不会包含失败的情况,因为这种情况是构建不出来的。

  • 在 Swift 中,无人类型是通过一个不包含任意成员的 enum 实现的:

    public enum Never {}
    复制代码
  • Swift 在区分各种“无”类型上非常严密。除了 nilNever,还有 Void ,Void 是空元祖 (tuple) 的另一种写法:

    public typealias Void = ()
    复制代码

    Swift 对“东西不存在”(nil),“存在且为空”(Void) 以及“不可能发生”(Never) 这几个概念进行了仔细的区分。

  • guard 是一个明确的型号,它暗示我们“只在条件成立的情况下继续”。Swift 编译器还会检查你是否在 guard 块中退出了当前作用域,如果没有的话,你会得到一个编译错误。因为可以得到编译器帮助,所以尽量选择使用 guard,即便 if 也可以正常工作。

可选链

  • Objective-C 中,对 nil 发消息什么都不会发生。Swift 里,我们可以通过“可选链 (optional chaining)” 来达到同样的效果:

    delegate?.callback()
    复制代码

nil 合并运算符

  • 和 || 操作符一样, ?? 操作符使用短路求值 (short circuiting)。当我们用 l ?? r 时,只有当 l 为 nil 时,r 的部分才会被求值。这是因为在操作符的函数声明中,对第二个参数使用了 @autoclosure

在字符串插值中使用可选值

  • Swift 的 ?? 运算符不支持两侧的类型不匹配的操作。只是为了在字符串插值中使用可选值这一特殊目的的话,添加一个 ??? 运算符也很简单

    infix operator ???: NilCoalescingPrecedence
    public func ???<T>(optional: T?, defaultValue: @autoclouse () -> String) -> String {
      switch optional {
        case let value?:return String(describing: value)
        case nil: return defaultValue()
      }
    }
    复制代码

可选值map

  • Optional.map 和数组以及其他序列里的map方法非常类似。但是与序列中操作一系列值所不同的是,可选值的 map 只会操作一个值,那就是该可选值中的那个可能存在的值。你可以把可选值当做一个包含零个或者一个值的集合,这样 map 要么在零个值的情况下不做处理,要么在有值的时候会对其进行转换。

可选值flatmap

let stringNumbers = ["1","2","3","foo"]
let x = stringNumbers.first.map { Int($0) } // Optional(Optional(1))
复制代码

flatMap 可以把结果展平为单个可选值。这样以来,y 的类型将会是 Int? :

let y = stringNumbers.first.flatMap { Int($0) } // Optional(1)
复制代码

使用 compactMap 过滤 nil

  • compactMap —— 将那些 nil 过滤出去并将非 nil 值进行解包的 map

可选值判等

  • 当我们在使用一个非可选值的时候,如果需要匹配成可选值类型,Swift 总是会将它“升级”为一个可选值。

可选值比较

  • 想要在可选值之间进行除了相等之外的关系比较的话,现在你需要先对它们进行解包,然后明确地指出 nil 要如何处理。

强制解包的时机

当你能确定你的某个值不可能是 nil 时可以使用叹号,你当会希望如果它意外是 nil 的话,程序应当直接挂掉。

改进强制解包的错误信息

  • 加一个 !! 操作符,它将强制解包和一个更具有描述性质的错误信息结合在一起,当程序意外退出时,这个信息也会被打印出来:

    infix operator !!
    
    func !!<T>(wrapped: T?,failureText: @autoclouse () -> String) -> T {
      if let x = wrapped { return x }
      fatalError(failureText())
    }
    复制代码
    let s = "foo"
    let i = Int(s) !! "Expecting integer, got \"\(s)\""
    复制代码

在调试版本中进行断言

  • 实现一个疑问感叹号 !? 操作符——将这个操作符定义为对失败的解包进行断言,并且在断言不触发的发布版本中将值替换为默认值:

    infix operator !?
    
    func !?<T: ExpressibleByIntegerLiteral> 
    	(warpped: T?, failureText: @autoclouse () -> String) -> T
    {
      assert(wrapped != nil, failureText())
      return wrapped ?? 0
    }
    复制代码

    现在,下面的代码将在调试时触发断言,但在发布版本中打印0:

    let s = "20" 
    let i = Int(s) !? "Expecting integer, got \"\(s)\""
    复制代码
  • 想要挂起一个操作我们有三种方式。首先,fatalError 将接受一条消息,并且无条件地停止操作。第二种选择,使用 assert 来检查条件,当条件结果为 false 时,停止执行并输出信息。在发布版本中,assert 会被移除掉,也就是说条件不会被检测,操作也永远不会挂起。第三种方式是使用 precondition,它和 assert 有一样的接口,但是在发布版本中不会被移除,也就是说,只要条件被判定为 false,执行就会被停止。

隐式解包可选值

为什么会要用到隐式可选值呢?

原因1:暂时来说,我们可能还需要到 Objective-C 里去调用那些没有检查返回是否存在的代码;或者你会调用一个没有针对 Swift 做注解的 C 语言的库。

原因2:因为一个值只是很短暂地成为nil,在一段时间后,它就再也不会是nil。最常见的情况就是两阶段初始化 (two-phase initialization)。

隐式可选值行为

虽然隐式解包的可选值在行为上就好像是非可选值一样,不过你依然可以对它们使用可选链,nil 合并,if let,map 或者将它们与 nil 比较,所有的这些操作都是一样的:

var s: String! = "Hello"
s?.isEmpty // Optional(false)
if let s = s { print(s) } // Hello
s = nil
s ?? "Goodbye" // Goodbye
复制代码
文章分类
iOS
文章标签