前言
本篇文章的提纲
1.举一个例子,理解函数式编程
2.用函数式编程的思想,做一个可以随意组合的动画库
什么是函数式编程?
关于函数式编程,网上有很多。浏览众多的文章后,对于函数式编程有了深刻的概念(其实就只记住了一句话): 函数是第一公民(函数可以当作参数,返回值。可以由多个低级的函数组合成一个高级的函数)。
下面我们来看一个例子
在图-1中,我们定义一个叫 AddGroup(加法组合) 的函数 。
很多人第一眼看到就觉得这就是一个闭包或者block嘛,还故意打码,搞出一副很深奥的样子。
虽然这是一个block,但是不能把它理解为block,因为当你认为这是block时,你就会想到回调。(反正我在网上看到这些例子的时候,就陷死在block的思维里了)。
这里我们把它理解为函数,或者公式,一套加法组合的公式。
在图-2中,我们定义生成一个加法组合公式的func ,我暂时把实现的细节马赛克了,因为我第一次看到这些例子的时候,陷死在了那些实现的细节里。
接下来就是开始使用这些函数
再用个高级一点的
上图-4中有一个叫compose的方法,作用是将add2和add3结合起来,组合成一个新的AddGroup。
第一个例子已经举完,下面就是源代码,大家可以直接复制粘贴到playground上去运行。在看下面源代码前可以先尝试着自己去实现。
//: Playground - noun: a place where people can play
import UIKit
typealias AddGroup = (Int)->Int //理解为的一系列加法组合的函数
func compose(_ addGroup1 : @escaping AddGroup , _ addGroup2 : @escaping AddGroup) -> AddGroup {
return { a in
return addGroup1(addGroup2(a))
}
}
func add(_ num : Int) -> AddGroup {
return { a in
return a + num
}
}
extension Int {
//给Int扩展一个执行AddGroup这个函数的方法
func excute(addGroup : AddGroup) -> Int {
return addGroup(self)
}
}
let add2 : AddGroup = add(2)//生成一个叫add2(加2)的AddGroup
let add3 : AddGroup = add(3)//生成一个叫add3(加3)的AddGroup
let addNew : AddGroup = compose(add2, add3)
let result = 1.excute(addGroup: addNew)//让1去执行这个AddGroup,得到结果为6
print("结果为 :",result)
开始做一个可以任意组合的动画库
先看一看这个库是如何调用的。
如上图-5所示。=> 这个符号表示,执行完前一个动画后再执行下一个动画。 + 加号符号表示 ,两个动画一起执行 。 + 的优先级高于 => , 先执行加号 ,在执行 =>。
看一看执行的效果
这算是动画比较多的了,但是调用起来还是比较简单,可读性也比较高的。
在来一个:
代码部分
首先我们定义一个要存储动画的类型
typealias AnimatState = (UIView) -> UIView
生成一个移动动画的方法,这里有个view.animCount , 我在后面会讲。
func move(to point: CGPoint , with duration : TimeInterval) -> AnimatState {
return { view in
view.animCount += 1
UIView.animate(withDuration: duration, delay: 0, options: [] , animations: {
view.frame.origin = point
}, completion: { _ in
view.frame.origin = point
view.animCount -= 1
})
return view
}
}
生成透明,旋转,缩放一系列的动画
透明
func fade(_ alpha: CGFloat , with duration : TimeInterval) -> AnimatState{
return { view in
view.animCount += 1
UIView.animate(withDuration: duration, delay: 0, options: [] , animations: {
view.alpha = alpha
}, completion: { _ in
view.alpha = alpha
view.animCount -= 1
})
return view
}
}
缩放
func scale(to scale: CGFloat , with duration : TimeInterval) -> AnimatState {
return { view in
view.animCount += 1
UIView.animate(withDuration: duration, delay: 0, options: [] , animations: {
view.transform = CGAffineTransform.init(scaleX: scale, y: scale)
}, completion: { _ in
view.transform = CGAffineTransform.init(scaleX: scale, y: scale)
view.animCount -= 1
})
return view
}
}
旋转
func rotate(by angle: CGFloat , with duration : TimeInterval ) -> AnimatState {
return { view in
view.animCount += 1
UIView.animate(withDuration: duration, delay: 0, options: [] , animations: {
view.transform = .init(rotationAngle: angle)
}, completion: { _ in
view.transform = .init(rotationAngle: angle)
view.animCount -= 1
})
return view
}
}
再生成一个空动画
func emptyAnim(_ duration : TimeInterval , closure : (()->Void)?) -> AnimatState{
return { view in
view.animCount += 1
closure?()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + duration, execute: {
view.animCount -= 1
})
return view
}
}
一些最基本的动画就做完了。
接下来就开始做重载 + 操作符 , 加号表示的是将两个动画结合成一个能同时执行这两个小动画的大动画
func + (left : @escaping AnimatState , right : @escaping AnimatState) -> AnimatState {
return { view in
return right(left(view))
}
}
重载 => 操作符 ,=> 表示的是先执行左边的,然后再执行右边的。
func => (left : @escaping AnimatState , right :@escaping AnimatState) -> AnimatState {
return { view in
view.animCompleteHandles.append( { [weak view] in
guard view != nil else { return }
_ = right(view!)
})
return left(view)
}
}
animCompleteHandles 是UView extension 的一个用来存放动画完成以后要做的动画回调的数组。 看一看它的实现
fileprivate var anim_comptele_handle_key : Void!
extension UIView {
var animCompleteHandles : [AnimComleteHandle] {
set{
objc_setAssociatedObject(self, &anim_comptele_handle_key, ( newValue as Any ), .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
get{
return (objc_getAssociatedObject(self, &anim_comptele_handle_key) as? [AnimComleteHandle]) ?? [AnimComleteHandle]()
}
}
}
接下来,我们还需要一个东西来判断何时做完动画。这里我用的是模仿引用计数的方式, 就是前面提到的view.animCount,当执行一个动画的时候,计数 +1 , 执行执行完后计数 -1。当计数等于0时,我们要调用animCompleteHandles数组里面的最后一个元素进行回调,然后销毁。
extension UIView {
fileprivate var animCount : Int {
set{
if newValue < 0 {
objc_setAssociatedObject(self, &anim_count_key, 0, .OBJC_ASSOCIATION_COPY_NONATOMIC)
return
}
objc_setAssociatedObject(self, &anim_count_key, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
if newValue == 0 {
animCompleteHandles.popLast()?()
}
}
get{
return ( objc_getAssociatedObject(self, &anim_count_key) as? Int ) ?? 0
}
}
}