SketchK's Studio.

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

字数统计: 967阅读时长: 3 min
2019/11/10 Share

每天了解一点不一样的 Swift 小知识

代码截图

01.png

代码出处: Swift Tips 027 by John Sundell

小笔记

这段代码在说什么

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

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

运算符也是一等公民

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 !

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 类型的话,你会发现这是一个非常贴心的小助手!

CATALOG
  1. 1. 代码截图
  2. 2. 小笔记
    1. 2.1. 这段代码在说什么
    2. 2.2. 运算符也是一等公民
    3. 2.3. One More Thing