添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
老实的充值卡  ·  DeleteWebCCRuleV2 - ...·  昨天    · 
阳刚的青蛙  ·  Geometry—ArcMap | 文档·  昨天    · 
留胡子的红薯  ·  Problem with ...·  昨天    · 
怕老婆的皮带  ·  rc-tree-select - npm·  12 小时前    · 
腹黑的书包  ·  USAJOBS - Search·  4 月前    · 
暗恋学妹的稀饭  ·  Error linking c++ ...·  9 月前    · 
威武的蜡烛  ·  Temas Para Campanhas ...·  11 月前    · 
var array = ["one","two","three"]
switch array.firstIndex(of: "four") {
case .some(let idx):
    array.remove(at: idx)
case .none:
    break // 什么都不做

或者这样写:

var array = ["one","two","three"]
switch array.firstIndex(of: "four") {
case let idx?:
    array.remove(at: idx)
case nil:
    break // 什么都不做

3. 可选值概览

 

if let

检查可选值是否为 nil,如果不是 nil,便会解包可选值。

var array = ["one", "two", "three", "four"]
// 可以把布尔限定语句与 if let 搭配在一起使用
if let idx = array.firstIndex(of: "four"), idx != array.startIndex {
    // idx 只在这个 if let 语句的作用域中有效
    array.remove(at: idx)

可以在同一个 if 语句中绑定多个值,并且在后面的绑定中可以使用之前成功解包出来的结果。

if let url = URL(string: urlString), 
    url.pathExtension == "png", // 多个 let 的任意部分也能拥有布尔值限定的语句
    let data = try? Data(contentsOf: url), // 通过 try? 来转变为一个可选值
    let image = UIImage(data: data) {
    let view = UIImageView(image: image)

while let

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

// 可以在可选绑定后面添加一个布尔值语句
while let line = readLine(), !line.isEmpty {
    print(line)

还可以使用迭代器:

let array = [1, 2, 3]
// 创建迭代器
var iterator = array.makeIterator() 
// 迭代器中的 next 方法将不断返回序列中的值,
// 并在序列中的值被耗尽的时候,返回 nil
while let i = iterator.next() {
    print(i, terminator: " ")

for 循环也支持布尔语句,只是要在布尔语句之前,使用 where 关键字:

for i in 0..<10 where i % 2 == 0 {
    print(i, terminator: " ")

将上面的 for 循环用 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 循环加上一个迭代器的简写方式
var iterator = maybeInts.makeIterator()
while let maybeInt = iterator.next() {
    print(maybeInt, terminator: " ") // 得到两个整数值和一个 `nil`

由于 next 方法会把序列中的每个元素包装成可选值,所以 iterator.next() 函数返回的其实是一个 Optional<Optional<Int>> 值,或者说是一个 Int??

请注意,这本书里面的 高级玩法 来了:

只对非 nil 的值做 for 循环

for case let i? in maybeInts { // 或者 for case let .some(i) in maybeInts
    // i 将是 Int 值,而不是 Int?
    print(i, terminator: " ")

x? 这个模式,它只会匹配那些非 nil 的值,是 .some(x) 的简写形式。

只对 nil 的值做 for 循环

for case nil in maybeInts {
    // 将对每个 nil 执行一次 
    print("No value")
let j = 5
if case 0..<10 = j {
    print("\(j) 在范围内")
} // 5 在范围内

if var and while var

let number = "1"
if var i = Int(number) {
    i += 1
    print(i)

请注意,任何对 i 的改变将不会影响到原来的可选值。可选值是值类型,解包一个可选值做的事情是将它里面的值复制出来。

解包后可选值的作用域

extension String {
    var fileExtension: String? {
        let period: String.Index
        if let idx = lastIndex(of: ".") {
            period = idx
        } else {
            return nil
        let extensionStart = index(after: period)
        return String(self[extensionStart...])

虽然可以成功解包并获得关联值,但是上面这种写法过于丑陋。

extension String {
    var fileExtension: String? {
        guard let period = lastIndex(of: ".") else {
            return nil
        let extensionStart = index(after: period)
        return String(self[extensionStart...])

guard - else

  • guard 能够接受任何在普通的 if 语句中能接受的条件。
  • 条件不成立时,提前退出。
  • 除了书上说的这两点,我个人觉得 guard-else 还有以下优点:

  • 可以有效地减少因大量使用 if - else 而导致的代码块缩进,使代码更优雅、易读;
  • 可以有效地提升防御式编程思维,督促开发者多思考 条件不成立 的情况;
  • Swift 中的“无”类型

  • “东西不存在”(nil)
  • “存在且为空”(Void), public typealias Void = ()
    • 常用做那些不返回任何东西的函数的返回值。
  • “不可能发生” (Never), public enum Never { }
    • 一个返回 Never 的函数用于通知编译器:它绝对不会返回。一种是像 fatalError 那样表示程序失败的函数,另一种是像 dispatchMain 那样 运行在整个程序生命周期的函数。

    可选链

    delegate?.callback(),加上 ? 来表示你正在链接这个可选值。

    这里有一个值得注意的示例,它可以帮助你加深对 ? 的理解。

    var a: Int? = 5
    a? = 10
    a // Optional(10)
    var b: Int? = nil
    b? = 10
    b // nil
    

    前一种写法无条件地将一个新值赋给变量,而后一种写法只在 b 的值在赋值发生前不是 nil 的时候才生效。

    我运行过这段代码,确实输出了和注释中的内容一样的值,不信你也可以试试看~

    nil 合并运算符 ??

    在解包可选值的同时,为 nil 的情况设置一个默认值。

    let stringteger = "1"
    let number = Int(stringteger) ?? 0
    

    需要注意的是,合并操作也能够进行链接。

    let i: Int? = nil
    let j: Int? = nil
    let k: Int? = 42
    i ?? j ?? k ?? 0 // 按顺序合并,最终得到 42
    

    除此之外,如果你要处理的是双重嵌套的可选值,并且想使用 ?? 操作符的话,需要特别 小心区分 a ?? b ?? c 和 (a ?? b) ?? c。前者是合并操作的链接,而后者是先解包括号内的内容, 然后再处理外层:

    let s1: String?? = nil // nil
    (s1 ?? "inner") ?? "outer" // inner
    let s2: String?? = .some(nil) // Optional(nil)
    (s2 ?? "inner") ?? "outer" // outer
    

    最后,?? 操作符使用短路求值。因为在操作符的函数声明中,对第二个参数使用了 @autoclosure

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

    常见场景如下,通常还会有编译器的警告提示:

    let bodyTemperature: Double? = 37.0
    let bloodGlucose: Double? = nil
    print(bodyTemperature) // Optional(37.0)
    

    修正编译器警告的方式:

  • 显式地用 as Any 进行转换,使用 ! 对值进行强制解包 (如果你能确定该值不为 nil 时)
  • 使用 String(describing: …) 对它进行包装
  • 用 nil 合并运算符提供一个默认值
  • 书中提供了一个比较优雅的解决方案:

    infix operator ???: NilCoalescingPrecedence
    public func ???<T>(optional: T?, defaultValue: @autoclosure () -> String) 
    -> String {
        switch optional {
        case let value?: return String(describing: value)
        case nil: return defaultValue()
    print("Body temperature: \(bodyTemperature ??? "n/a")")
    // Body temperature: 37.0
    

    可选值 map

    只在可选值不为 nil 的时候才进行转换。

    var firstCharAsString: String? = nil
    if let char = characters.first {
        firstCharAsString = String(char)
    

    以上代码可以使用 map 进行精简:

    let firstChar = characters.first.map { String($0) } // Optional("a")
    

    map 的实现代码:

    extension Optional {
        func map<U>(transform: (Wrapped) -> U) -> U? {
            guard let value = self else { return nil }
            return transform(value)
    

    可选值 flatMap

    flatMap 可以把结果展平为单个可选值,避免多重嵌套的可选值(如:Int??)。

    这是之前的示例:

    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)
    

    flatMap 进行等价转换:

    let urlString = "https://www.objc.io/logo.png" 
    let view = URL(string: urlString)
                .flatMap { try? Data(contentsOf: $0) } 
                .flatMap { UIImage(data: $0) }
                .map { UIImageView(image: $0) }
    

    flatMap 的实现代码:

    extension Optional {
        func flatMap<U>(transform: (Wrapped) -> U?) -> U? {
            if let value = self, let transformed = transform(value) {
                return transformed
            return nil
    

    使用 compactMap 过滤 nil

    来看3个示例,它们都是为了求和:

    使用 for

    let numbers = ["1", "2", "3", "foo"] 
    var sum = 0
    for case let i? in numbers.map({ Int($0) }) {
        sum+=i 
    sum // 6
    

    使用 map

    numbers.map { Int($0) }.reduce(0) { $0 + ($1 ?? 0) } // 6
    

    使用 compactMap

    numbers.compactMap { Int($0) }.reduce(0, +) // 6
    

    结果一目了然,使用 compactMap 之后,代码简短优雅而且不易出错。

    自己实现 compactMap:

    extension Sequence {
        func compactMap<B>(_ transform: (Element) -> B?) -> [B] {
            // 使用 lazy 可以避免多个作为中间结果的数组的内存分配
            return lazy.map(transform).filter { $0 != nil }.map { $0! }
    

    可选值判等

    当比较两个可选值时,会有四种组合的可能性:

  • 两者都是 nil
  • 两者都有值;
  • 两者中有一个有值, 另一个是 nil
  • 对应的代码如下:

    extension Optional: Equatable where Wrapped: Equatable { 
        static func ==(lhs: Wrapped?, rhs: Wrapped?) -> Bool {
            switch (lhs, rhs) {
            case (nil, nil): return true
            case let (x?, y?): return x == y 
            case (_?, nil), (nil, _?): return false 
    

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

    如果没有隐式转换,你就必须写像是 myDict[“someKey”] = Optional(someValue) 这样的代码。

    提到字典,使用下标操作为字典的某个键赋值 nil 值得探讨一下:

    首先,直接赋值 nil,这个键会从字典中移除。

    可以采用以下方法:

    dictWithNils["two"] = Optional(nil) 
    dictWithNils["two"] = .some(nil)
    
    dictWithNils["two"]? = nil
    

    注意,”two” 这个键必须已经存在于字典中,然后才能使用可选链的方式来在获取成功后对值进行设置。

    可选值比较

    书中的建议:

    先解包,然后明确地指出 nil 要如何处理,避免意外的结果发生。

    4. 强制解包的时机

     

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

    每当你发现需要使用 ! 时,可以回头看看是不是真的别无他法了。

    改进强制解包的错误信息

    当程序因为强制解包而发生错误时,你从输出的 log 中无法通过描述知道原因是什么。

    书中给出的比较优雅的解决方案:

    infix operator !!
    func !! <T>(wrapped: T?, failureText: @autoclosure () -> 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>(wrapped: T?, failureText: @autoclosure () -> String) -> T {
        assert(wrapped != nil, failureText())
        return wrapped ?? 0
    let s = "20"
    let i = Int(s) !? "Expecting integer, got \"\(s)\""
    

    然后,对其他字面量转换协议进行重载,可以覆盖不少能够有默认值的类型: ExpressibleByArrayLiteral, ExpressibleByStringLiteral

    如果想要显式地提供一个不同的默认值,或者是为非标准的类型提供这个操作符,定义一个接受元组为参数的版本,元组包含默认值和错误信息:

    func !?<T>(wrapped: T?, nilDefault: @autoclosure () -> (value: T, text: String)) -> T {
        assert(wrapped != nil, nilDefault().text) 
        return wrapped ?? nilDefault().value
    // 调试版本中断言,发布版本中返回 5 Int(s) !? (5, "Expected integer")
    

    挂起一个操作的三种方式

  • fatalError,接受一条信息,并且 无条件地停止 操作。
  • 使用 assert 来检查条件,当条件结果为 false 时,停止执行并输出信息。在发布版本中,assert会被移除掉,条件不会被检测,操作也永远不会挂起。
  • 使用 precondition,它和 assert 有一样的接口,但是在发布版本中不会被移除。只要条件被判定为 false,执行就会被停止。
  • 5. 隐式解包可选值

     

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

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

  • Objective-C 中表示引用是否 可以为空的语法是最近才被引入的,以前除了假设返回的引用可能是 nil 引用以外,也没有什么好办法。
  • 所有人都已经习惯了 Objective-C 世界中对象 “可能为空” 的设定,因此把这样的返回值作为隐式解包可选值来使用是可以说得过去的。
  • 原因 2:因为一个值只是很短暂地为 nil,在一段时间后,它就再也不会是 nil。

  • 最常见的情况就是两阶段初始化 (two-phase initialization)。当你的类准备好被使用时,所有的隐式解包可选值都将有一个值。这就是 Xcode 和 Interface Builder 在 view controller 的生命周期中使用它们的方式。
  • 隐式可选值行为

    你依然可以对它们使用可选链, 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
    

    阅读本书使我获益良多,衷心地感谢优秀的内容生产者们!

    如需深入学习相关内容,推荐大家购买原版书籍 《Swift 进阶》
    请大家支持原创,用行动去鼓励高质量的内容生产者!

  •