见到 optional 的东西时我都会有种莫名的舒爽,因为我可以用 map 或者 flatMap 将它作为参数传给某个函数,并将结果又以同样的方式传递给下一个函数:

1
2
3
let newImage = imageThatIsOptional
.map(scale)
.map(rotate)

但偶尔我们会碰到一些拥有多个参数的函数,比如说 crop(_ image: UIImage, with rect: CGRect),这时链式调用就尴尬了。

曾几何时,我们是可以把 tuple 直接作为多参数函数的参数的,那个时候我们可以写成:

1
2
let tuple = (image, .zero)
crop(tuple) // now it would be error: missing argument for parameter 'with' in call

后来 Swift 取消了这个功能,我们不能再直接将 tuple 作为参数了。但是,我们依然能够通过 map 的方式,将一个 optional 的 tuple 作为参数传给函数。

1
2
let tuple: (UIImage, CGRect)? = (image, CGRect.zero)
tuple.map(crop)

很棒吧,但我们还需要解决一个问题,那就是怎么优雅地创建这个 tuple。John Sundell 给我们推荐了一个方法——再给 Optional 加个 flatMap

1
2
3
4
5
6
7
8
9
10
extension Optional {
func flatMap<T>(with expression: @autoclosure () -> T?) -> (Wrapped, T)? {
return flatMap { a in
return expression().map { b in return (a, b) }
}
}
}
let newImage1 = image.map(scale).flatMap(with: CGRect.zero).map(crop)
// 当值不是 optional 的时候,我们也可以强行让它变成 optional
let newImage2 = Optional(UIImage()).flatMap(with: CGRect.zero).map(crop)

当然我们也可以从方法下手,用 swift-overture 这种东西把 crop 变成只需要一个参数的方法。