Swift Tips 027 - Manipulating points, sizes and frames using math operators

289 阅读3分钟

代码截图

代码出处: Swift Tips 027 by John Sundell

小笔记

这段代码在说什么

这段代码在 CGSize 类型中重载了名为 * 的中缀运算符,新的定义使其能够按照右侧的值等倍扩大 CGSize 中的 width 和 height。

通过这个小 Tips 使代码的可读性变强,也更加简洁!

运算符也是一等公民

运算符在 Swift 中是一个函数,而函数在 Swift 中是一等公民,所以正是基于这两点,开发者可以在运算符这个点上做很多有意义的事情。而我最近就发现了一个关于自定义运算符的使用场景,这里与大家探讨一下。

我们都知道 Swift 里的 do, try, catch 在处理异常情况时十分有用,尤其在处理那些可以失败的异步操作时。 do, try, catch 的机制可以让我们在函数出现问题时,轻松的退出当前函数并执行一些相应的操作,例如我们从硬盘里读取笔记的数据模型(如同之前定义的 loadNote 函数一样)

假设我们有如下一段代码:

extension NoteManager {
    enum LoadingError: Error {
        case invalidFile(Error)
        case invalidData(Error)
        case decodingFailed(Error)
    }
}

class NoteManager {
    func loadNote(fromFileNamed fileName: String) throws -> Note {
        do {
            let file = try fileLoader.loadFile(named: fileName)
            do {
                let data = try file.read()
                do {
                    return try Note(data: data)
                } catch {
                    throw LoadingError.decodingFailed(error)
                }
            } catch {
                throw LoadingError.invalidData(error)
            }
        } catch {
            throw LoadingError.invalidFile(error)
        }
    }
}

上面的代码已经是一种朴素的不能再朴素的写法了,但我相信没人会喜欢读上面的代码,因为它让你很头大......

那么有什么办法能让代码变得更友善一点呢?这里我尝试采用新增自定义运算符的方式来解决这个问题!

下面的代码定义一个新的运算符 ~> 并重构了之前的代码。

至于为什么选择 ~>,是因为它很像 ->(函数返回值), 但是又不相同,这就意味着可能会返回与正常值不同的东西,例如一个 error !

infix operator ~>

func ~><T>(expression: @autoclosure () throws -> T,
           errorTransform: (Error) -> Error) throws -> T {
    do {
        return try expression()
    } catch {
        throw errorTransform(error)
    }
}

class NoteManager {
    func loadNote(fromFileNamed fileName: String) throws -> Note {
        let file = try fileLoader.loadFile(named: fileName) ~> LoadingError.invalidFile
        let data = try file.read() ~> LoadingError.invalidData
        let note = try Note(data: data) ~> LoadingError.decodingFailed
        return note
    }
}

看到了么?这里我们实现了 ~> 运算符,它是一个中缀运算符,左边是一个会抛出异常的表达式,右边是一个定义为 (error)->error 的表达式, 而整个运算符的返回值与左边表达式的返回值一致,不论是正常执行,还是出现异常!

也许你会好奇,为什么在调用 ~> 的时候,右边的表达式是 LoadingError.invalidFile 呢,这是因为在 Swift 中,带关联值的枚举本身也是一个函数,如果你有点忘了这一点,不妨看看之前的 Swift Tips 014 - Referring to enum cases with associated values as closures

代码是不是变得更加整洁了一些呢?当然这种新增的运算符也会增加大家的理解成本,不过为了更优雅,更整洁的代码,这似乎也是可以接受的!

总之,这就是今天我想分享的自定义运算符在 do, try, catch 里的一点实际应用。

One More Thing

如果你很喜欢今天的 Tips,不妨关注一下 CGOperators 这个仓库,它提供了许多与 Core Graphics 相关的数学操作符,尤其是你经常需要在代码里操作 CGPoint,CGSize 和 CGVector 类型的话,你会发现这是一个非常贴心的小助手!