闭包是swift中相当重要的一个概念,他不仅为swift开发自定义调用执行逻辑提供了方便。同时也是Swift中函数式编程之所以强大的一个重要原因。
这里我们就来探索一下swift闭包的一些用法。
首先从适用场景来讲,闭包基本上可以分为非逃逸闭包和逃逸闭包两种。这个概念在其他语言中是没有的。而swift中引出这个概念,从而让我们能够更好的理解闭包的生命周期。
非逃逸闭包
swift默认的闭包即为非逃逸闭包。其生命周期仅限于闭包参数所在函数的生命周期内有效。一旦超越此作用于,将会导致编译异常。主要体现在两个方面:
- 将非逃逸闭包作为参数赋值给对象的一个属性时,会报语法错误提示:
Assigning non-escaping parameter 'xxx' to an @escaping closure
- 将非逃逸闭包放在异步执行逻辑中使用则会出现如下语法错误:
Escaping closure captures non-escaping parameter 'xxx'
之所以存在这样的问题,就在与非逃逸闭包的作用域仅限于当前函数本身的生命周期,一旦作为属性或者异步逻辑中的调用存在,将扩大闭包本身的作用于,这与非逃逸闭包的作用于相违背。
例如如下用法即为错误:
func minMax(limit handle: (Int) -> Int) {
DispatchQueue.main.async {
_ = handle(1)
}
}
而正确的用法则为
func minMax(limit handle: (Int) -> Int) -> Int {
return = handle(1)
}
由此,也就引出了另一个需要讨论的话题逃逸闭包。
逃逸闭包
逃逸闭包可以干啥?简单来说就是非逃逸闭包不能干的两件事,逃逸闭包都能干。此外,非逃逸闭包能做的事情,逃逸闭包当然也能干了。
也就是对于逃逸闭包,可以做如下定义:
@propertyWrapper
class AgeWrapper {
var wrappedValue: Int {
get {
return age
}
set {
age = limitHandle(newValue)
}
}
var age: Int = 0
var limitHandle: (Int) -> Int
init(limit handle: @escaping (Int) -> Int) {
self.limitHandle = handle
print(handle(199))
}
}
然后执行如下调用
@AgeWrapper(limit: { max(0, min($0, 121)) }) var age: Int
print("old value -> \(age)")
age = 139
print("new value -> \(age)")
既然如此,是不是说,我们可以直接所有的都用逃逸闭包,而不需要使用非逃逸闭包了呢?当然不是的。试想,如果我们看到的所有暴露的涉及到闭包的函数,看到的都是逃逸闭包,那我们是不是会担心那些原本只作用于当前函数,而在函数结束即结束生命周期的地方,穿入的闭包参数的影响是否会扩大化,从而为编码带来大量的负面效应。因此,在使用闭包的时候,我们应该遵循一个原则:
能用非逃逸闭包就用非逃逸闭包,只有在无法满足这个条件的前提下才用逃逸闭包。
了解了闭包的两种类型之外。我们接下来还要讨论一个概念:自动闭包 @autoclosure。
自动闭包(@autoclosure)
为什么要把自动闭包与非逃逸闭包和逃逸闭包分列出来讲?主要原因在于:
- 自动闭包与非逃逸闭包和逃逸闭包在使用上,扩展了概念,使得这类闭包支持表达式方式传参。例如,我们定义了如下函数:
func makeAutoIncrement(closure: @autoclosure () -> Int) -> Int {
return closure()
}
在使用上,我们可以使用表达式的形式来传参:
var age = 30
age = makeAutoIncrement(closure: age + 5)
print(age)
age = makeAutoIncrement(closure: age + 3)
print(age)
这使得语法上,更像是传递一个值,但与值传递有所不同的是,自动闭包,会将穿入的数据计算时间后延,从而避免在某些条件下无需执行结果的情况下导致计算耗时。
- 自动闭包与非逃逸闭包和逃逸闭包又存在使用上的关联性。这表现在,对于自动闭包,默认其实也是一个非逃逸闭包。而,如果我们在自动闭包修饰符继续使用修饰符@escaping 那么我们将得到一个逃逸的自动闭包。如下:
class WPerson {
var age: Int
}
func setAutoIncrement(closure: @autoclosure @escaping () -> Int) {
handle = closure
}
然后进行如下调用
var person = WPerson()
setAutoIncrement(closure: person.age + 3)
person.age = handle()
print(person.age)
person.age = handle()
print(person.age)
我们会发现其打印结果如下:
3
6
变量捕获
在最后一个板块的知识,我们将来了解一下关于闭包中,值的捕获问题。
闭包对与外部变量的捕获,实质上是对变量的一个引用。在内部没有定义同名变量的情况下,闭包中对变量的修改将作用到变量本身。参考示例:
func makeHandle() -> () -> Int {
var value = 3
return {
value = value + 3
return value
}
}
我们做如下调用:
let handle = makeHandle()
print(handle())
print(handle())
运行得到如下结果:
6
9
最后,闭包还有许多的语法糖操作,如闭包表达式使用、尾随闭包等,就不在做详细介绍了。