原文: Swift: UIView Animation Syntax Sugar
做者: Andyy Hope
译者: kemchenjgit
Swift 代码里的闭包是很好用的工具, 它们是一等公民, 若是他们在 API 的尾部时还能够变成尾随闭包, 而且如今 Swift 3 里还默认为 noescape
以免循环引用.程序员
但每当咱们不得不使用那些包含了多个闭包参数的API的时候, 就会让这门优雅的语言变得很丑陋. 是的, 我说的就是你, UIView.github
class func animate(withDuration duration: TimeInterval, animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil)
UIView.animate(withDuration: 0.3, animations: { // 动画 }) { finished in // 回调 }
咱们正在混合使用多个闭包和尾随闭包, animation:
还拥有参数标签, 但 completion:
已经丢掉参数标签变成一个尾随闭包了. 在这种状况下, 我以为尾随闭包已经跟原有的函数产生了割裂感, 但我猜这是由于 API 的右尖括号跟右括号让我感受这个函数已经结束了:编程
}) { finished in // 糟透了
若是你不肯定什么是尾随闭包, 我有另外一篇文章解释它的定义和用法 Swift: Syntax Cheat Codesswift
另外一个就是 animation 的两个闭包是同一层级的, 而它们默认的缩进却不一致. 最近我感觉了一下函数式编程的伟大, 写函数式代码的一个很爽的点在于把那些序列的命令一条一条经过点语法罗列出来:api
[0, 1, 2, 4, 5, 6] .sorted { $0 < $1 } .map { $0 * 2 } .forEach { print($0) }
那为何不能把带两个闭包的 API 用一样的方式列出来?promise
若是你不理解
$0
语法, 我有另外一篇文章介绍如何它们的含义和语法 Swift: Syntax Cheat Codes闭包
UIView.animate(withDuration: 0.3, animations: { // 动画 }, completion: { finished in // 回调 })
我想借鉴一下函数式编程的语法, 强迫本身去手动调整代码格式而不是用 Xcode 默认的自动补齐. 我我的以为这样子会让代码可读性更加好但这也是一个很机械性的过程. 每次我复制粘贴这段代码的时候, 缩进老是会乱掉, 但我以为这是 Xcode 的问题而不是 Swift 的.app
let animations = { // 动画 } let completion = { (finished: Bool) in // 回调 } UIView.animate(withDuration: 0.3, animations: animations, completion: completion)
这篇文章开头我提到闭包是Swift 的一等公民, 这意味着咱们能够把它赋值给一个变量而且传递出去. 我以为这么写并不比上一个例子更具可读性, 并且别的对象只要想要就能够去接触到这些闭包. 若是必定要我选择的话, 我更乐意使用上一种写法.iview
就像许多程序员同样, 我会强迫本身去思考出一个方式去解决这个很常见的问题, 而且告诉本身, 久而久之我能够节省不少时间.
UIView.Animator(duration: 0.3) .animations { // Animations } .completion { finished in // Completion } .animate()
就像你看到的, 这种语法和结构从 Swift 函数式的 API 里借鉴了不少. 咱们把两个闭包的看做是集合的高等函数, 而后如今代码看起来好不少, 而且在咱们换行和复制粘贴的时候, 编译器也会根据咱们想要的那样去工做(译者注: 这应该跟 IDE 的 formator 有关, 而不是编译器, 毕竟 Swift 不须要游标卡尺?)
"久而久之我能够节省不少时间"
class Animator { typealias Animations = () -> Void typealias Completion = (Bool) -> Void private var animations: Animations private var completion: Completion? private let duration: TimeInterval init(duration: TimeInterval) { self.animations = {} // 译者注: 把 animation 声明为 ! 的其实就能够省略这一行 self.completion = nil // 这里其实也是能够省略的 self.duration = duration } ...
这里的 Animator 类很简单, 只有三个成员变量: 一个动画时间和两个闭包, 一个初始化构造器和一些函数, 待会咱们会讲一下这些函数的做用. 咱们已经用了一些 typealias
提早定义一些闭包的签名, 但这是一个提升代码可读性的好习惯, 而且若是咱们在多个地方用到了这些闭包, 须要修改的时候, 只须要修改定义, 编译器就会替咱们找出全部须要调整的地方, 而不是由咱们本身去把全部实现都给找出来, 这样就能够帮助咱们减小出错的概率.
这些闭包变量是可变的(用 var 声明), 因此咱们须要把他们保存在某个地方, 而且在实例化以后去修改它, 但同时他们也是 private
私有的, 避免外部修改. completion
是 optional 的, 而 animation
不是, 就像 UIView
的官方 API 那样. 在咱们初始化构造器的实现里, 咱们给闭包一个默认值避免编译器报错.
func animations(_ animations: @escaping Animations) -> Self { self.animations = animations return self } func completion(_ completion: @escaping Completion) -> Self { self.completion = completion return self }
闭包集合的实现很是简单, 接受一个闭包的参数, 而后把它赋值给相应的变量就好了.
最棒的一点是, 这些 API 都会把返回本身, 这样咱们就能够链式地调用:
let numbers = [0, 1, 2, 4, 5, 6] // Returns Array .sorted { $0 < $1 } // Returns Array .map { $0 * 2 } // Returns Array
然而, 若是链式调用的最后一个函数返回一个对象, 那咱们就能够把它赋值给某个变量, 而后继续使用, 在这里咱们把结果赋值给了 numbers.
而若是函数返回空值那咱们就没必要赋值给变量了:
[0, 1, 2, 4, 5, 6] // Returns Array .sorted { $0 < $0 } // Returns Array .map { $0 * 2 } // Returns Array .forEach { print($0) } // Returns Void
func animate() { UIView.animate(withDuration: duration, animations: animations, completion: completion) }
就像函数式同样, 前面全部的调用都是为了最后的结果, 这并非一件坏事. Swift 容许咱们做为思考者, 工匠和程序员去从新想象和构建咱们所须要的工具.
extension UIView { class Animator { ...
最后, 咱们把 Animator
的放到 UIView 的 extension 里, 主要是由于 Animator
是强依赖于 UIView 的, 而且内部函数须要获取到 UIView 内部的上下文, 咱们没有任何须要把它独立成一个类.
UIView.Animator(duration: 0.3, delay: 0, options: [.autoreverse]) UIView.SpringAnimator(duration: 0.3, delay: 0.2, damping: 0.2, velocity: 0.2, options: [.autoreverse, .curveEaseIn])
还有一些参数是咱们须要传递给 animation 的 API 里的,查看这里的文档就能够了. 咱们还能够继承 Animator 类再建立一个 SpringAnimator
去知足咱们平常的绝大部分需求.
就像以前那样, 我提供了一个 playgrounds 在 Github 上, 或者看一下这里的 Gist 也能够, 这样你就没必要打开 Xcode 了.
若是你喜欢这篇文章的话, 也能够看一下我别的文章, 或者你想在你的项目里使用这个方法的话, 请在 Twitter 上发个推@我或者关注我, 这都会让我很开心.
翻译这篇文章的时候, 我很偶然地在简书上看到了 Cyandev 的 Swift 中实现 Promise 模式 (我很喜欢他写的文章), 发现其实能够再优化一下
你们有没有印象 URLRequest 的写法, 典型的写法是这样子的:
let url = URL() let task = URLSession.shared.dataTask(with: url) { (data, response, error) in // 回调 } task.resume()
刚接触这个 API 的时候, 我常常忘记书写后面那句 task.resume()
, 虽然这么写很 OO, 可是我仍是很讨厌这种写法, 由于生活中任务不是一个可命令的对象, 我命令这个任务执行是一件很违反直觉的事情
一样的, 我也不太喜欢原文里最后的那一句 animate
, 因此咱们能够用 promise 的思路去写:
class Animator { typealias Animations = () -> Void typealias Completion = (Bool) -> Void private let duration: NSTimeInterval private var animations: Animations! { didSet { UIView.animateWithDuration(duration, animations: animations) { success in self.completion?(success) self.success = success } } } private var completion: Completion? { didSet { guard let success = success else { return } completion?(success) } } private var success: Bool? init(duration: NSTimeInterval) { self.duration = duration } func animations(animations: Animations) -> Self { self.animations = animations return self } func completion(completion: Completion) -> Self { self.completion = completion return self } }
我把原有的 animate
函数去掉了, 加了一个 success
变量去保存 completion 回调的参数.
这里会有两种状况: 一种是动画先结束, completion
还没被赋值, 另外一种状况是 completion
先被赋值, 动画还没结束. 个人代码可能有一点点绕, 主要是利用了 Optional chaining 的特性, completion
其实只会执行一次.
稍微思考一下或者本身跑一下大概就能理解了, 这里其实我也只是简单的处理了一下时序问题, 并不完美, 仍是有极小的几率会出问题, 但鉴于动画类 API 的特性, 两个闭包都会按顺序跑在主线程上, 并且时间不会设的特别短, 因此正常状况是不会出问题
具体调用起来会是这个样子, 这个时候再把这个类命名为 Animator 其实已经不是很适合:
UIView.Animator(duration: 3) .animations { // 动画 } .completion { // 回调 }
虽然只是少了一句代码, 可是我以为会比以前更好一点, 借用做者的那句话 "save time in the long run"