Swift Tips 027 - Manipulating points, sizes and frames using math operators
每天了解一点不一样的 Swift 小知识
代码截图
小笔记
这段代码在说什么
这段代码在 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 类型的话,你会发现这是一个非常贴心的小助手!