对于程序员来说,如果你精通了闭包技术,那么毫无疑问,你的技术水平会得到大踏步的前进。
我们这次的主题是聊聊swift闭包的几个概念Capturing Values
、Nonescaping Closures
和``Autoclosures`。
Capturing Values
在电影超人3当中,Gus Gorman修改了公司的程序,在给每人结算工资的时候,偷偷的将其中微小的一部分转到自己的账户下,从而累积了巨额财富。我们今天就用闭包的Capturing Values
性质来实现这个算法。值得一提的是,原作者在算法中考虑了加班工资的部分,因考虑具体国情,根据实际情况,我已经把加班工资的相关代码去掉了。
struct PennyShaver {
static var stolenSoFar = 0.0
static var numberOfPeopleStolenFrom = 0
var amountToSteal = 0.01
func createPaymentCalculator() -> (Double,Double) -> Double {
func calculatePayroll(hourlyRate: Double,hours: Double) -> Double {
let payrollAmount = hourlyRate * hours
PennyShaver.stolenSoFar += amountToSteal
PennyShaver.numberOfPeopleStolenFrom += 1
return payrollAmount - amountToSteal
}
return calculatePayroll
}
}
代码超级简单,我们在struct
中定义了createPaymentCalculator
方法,该方法会返回一个闭包工资计算器calculatePayroll
,该计算器用公式: 工作时间 * 每小时费用 来计算每个人的工资。
这个计算器有一个厉害的特性,就是它能够捕捉context
(PennyShaver)中的变量stolenSoFar、numberOfPeopleStolenFrom、amountToSteal
用来计算 偷了多少人的钱 以及 偷到多少钱。于是我们可以这样展开“偷钱”工作。
var pennyShaver = PennyShaver(amountToSteal: 0.10)
let calculator = pennyShaver.createPaymentCalculator()
let amount = calculator(70,40) // 2799.9 偷到了一分钱
我们可以看到,闭包可以把变量amountToSteal
带出该变量所属的正常生命周期之外,仍然进行读写维护操作,这个特性就是Capturing Values
.这是闭包最重要的特性,也是闭包之所以称之为闭包的重要原因。
Nonescaping Closures
在聊Nonescaping Closures(非逃逸型闭包)之前,咱们都先说说escape型闭包。如果一个闭包被作为参数传递给一个函数,如果该函数允许这个闭包在函数自己的生命周期结束以后仍然可以被使用,那么我们就说这个作为参数的闭包是:“可逃逸的”。举一个例子就更清楚了。
var callbackListenerList: [(Bool,String) -> Void] = []
func registerListenerCallbacks(callback: (Bool,String) -> Void) {
callbackListenerList.append(callback)
}
registerListenerCallbacks({(_,message) -> Void in
print(message)
})
registerListenerCallbacks函数接收一个闭包,接着它将这个闭包append到一个数组中,所以即使这个函数的生命周期结束了。我们在未来的某一时刻让然可以利用这个闭包来工作。比方说像下面这样。
let callback = callbackListenerList.first!
callback(true,"Hello,world")
这个时候,我们称参数闭包“逃逸了”,逃逸型闭包非常适用于异步场景。
如果你此时屏蔽这种逃逸现象(禁止闭包在回调数组中工作),那么就可以使用关键字@noescape
来修饰闭包参数,这个关键字就是像编译器声明,函数结束以后,我不在需要这个闭包了。
func registerListenerCallbacks(@noescape callback: (Bool,String) -> Void) {
callbackListenerList.append(callback) // error !!!!!!
}
如果我们这样修改了registerListenerCallbacks方法,那么就会收到编译器的错误提示。通过
@noescape`,编译器就会非常明确自己应该如何进一步优化代码了,否则编译器在没有收到我们明确的态度之前,它必须给我们保留各种可能的余地。
Autoclosures
在我们传递闭包的时候,语法要求我们给闭包的外侧添加一对{}
,就像下面这个代码片段所描述的那样。
func printString(f: () -> ()) {
f()
}
printString({print("dddd")})
函数printString接收一个闭包作为参数,当我们调用printString
的时候,我们需要用到一个对括号{print("dddd")}
,如果我们使用@autoclosure
来修饰参数的话,我们可以省略这对括号。
func printString(@autoclosure f: () -> ()) {
f()
}
printString(print("dddd"))
@autoclosure
可以有效的简化我们的代码,但随之而来的问题是,它降低了代码的可读性,所以我们要在简洁与可读性之间选择好一个平衡点。
另外值得注意点的是@autoclosure
默认是nonescape
的,如果我们需要在@autoclosure
的基础上做进一步的说明,指明这个闭包参数是可逃逸的。
func printString(@autoclosure(escaping) f: () -> ()) {
f()
}
总结
虽然有些内容我们在实际的工作中并不经常使用,但值得我们注意的一点是:有些内容是在潜移默化的影响着我们,例如:审美,我们也很难说清楚我们自己的审美标准是从何而来,技术也同样,想更上一层楼,我们就需要积累那些可能会潜移默化影响我们的东西。
内容参考:
Swift Closures — Everyday Gems Part 1 of 2
Swift Closures — Everyday Gems Part 2 of 2