Swift中的闭包
闭包是 Swift 中一种高级数据结构,它允许在函数内部访问函数外部的变量和参数。在 Swift 中,闭包是由闭包表达式创建的,闭包表达式是一个包含一个或多个匿名函数的表达式。
闭包的定义
闭包表达式是一个包含一个或多个匿名函数的表达式,它可以访问函数外部的变量和参数。闭包可以用于修改外部函数的参数或变量,或者返回一个新的值。
我们看一下官方给的示例
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
var fn = makeIncrementer();
在代码中,incrementer
作为一个闭包,也是一个函数。而且incrementer
的生命周期比makeIncrementer
要长。当makeIncrementer
执行完毕后,内部包含的变量runningTotal
也随之消失,但是incrementer
有可能还没执行。要想incrementer
能够执行,这是就需要捕获runningTotal
到incrementer
内部中,因此构成闭包有两个关键点,一个是函数,另外一个是能够捕获外部变量或者常量。
闭包表达式
闭包表达式是 Swift 中一种用于创建闭包的表达式。闭包表达式包含一个或多个匿名函数,这些匿名函数可以访问外部函数的变量和参数。闭包表达式可以用于修改外部函数的变量或参数,或者返回一个新的值。
在 Swift 中,闭包表达式可以用方括号 ()
来表示,例如:
let add = { x in x + 1 }
print(add(2)) // 输出 3
在上面的代码中,add
是一个闭包表达式,它包含一个匿名函数 { x in x + 1 }
。这个匿名函数可以访问外部函数 add
的变量 x
,并在函数内部增加 1
。最后,闭包表达式将返回一个新的值,这个新值是 x + 1
。
除了使用方括号 ()
来表示闭包表达式外,Swift 中还可以使用简写方式来表示闭包表达式。简写方式使用两个引号包裹闭包表达式,例如:
let add = { x, y in x + y }
print(add(2, 3)) // 输出 5
在上面的代码中,add
是一个闭包表达式,它包含一个匿名函数 { x, y in x + y }
。这个匿名函数可以访问外部函数 add
的变量 x
和 y
,并在函数内部增加 y
。最后,闭包表达式将返回一个新的值,这个新值是 x + y
。
闭包和闭包表达式的区别
- 闭包表达式是一个包含匿名函数的表达式,而闭包则是闭包表达式的值。
- 闭包表达式的匿名函数可以访问外部函数的变量和参数,而闭包只能在当前作用域内访问。
- 闭包表达式可以包含一个或多个参数,而闭包只能包含一个参数。
闭包的使用
闭包可以用于修改外部函数的参数或变量,或者返回一个新的值。在 Swift 中,闭包的使用非常方便,只需要在函数外部定义一个闭包表达式,然后在函数内部调用这个闭包表达式即可。
- 闭包当做变量
var closure : (Int) -> Int = { (age: Int) in
return age
}
- 闭包声明成一个可选类型
var closure : ((Int) -> Int)?
closure = nil
- 闭包当做一个常量(一旦赋值之后就不能改变了)
let closure: (Int) -> Int
closure = {(age: Int) in
return age
}
- 闭包当做函数参数
func test(n:() -> Int) {
print(n());
}
var age = 10
test(n: {() -> Int in
age += 1
return age
})
test {() -> Int in
age += 1
return age
}
尾随闭包
尾随闭包是 Swift 中一种高级闭包特性,它可以在函数返回后继续执行。尾随闭包可以用于实现循环引用等功能。在 Swift 中,可以使用闭包表达式定义一个尾随闭包,然后将这个闭包表达式作为参数传递给另一个函数。
函数
func test(_ a: Int, _ b: Int, _ c: Int, by: (_ item1: Int, _ item2: Int, _ item3: Int) ->Bool) -> Bool{
return by(a, b, c)
}
- 未使用尾随闭包
test(10, 20, 30, by: {(_ item1: Int, _ item2: Int, _ item3: Int) -> Bool in
return (item1 + item2 < item3)
})
- 使用尾随闭包
test(10, 20, 30){(_ item1: Int, _ item2: Int, _ item3: Int) in
return item1 + item2 < item3
}
func exec(fn:(Int,Int) -> Int) {
print(fn(1,2))
}
exec(fn: {$0 + $1})
exec() {$0 + $1}
exec {$0 + $1}
闭包表达式简写
在 Swift 中,可以使用简写方式来表示闭包表达式。简写方式使用两个引号包裹闭包表达式,例如:(x, y) in x + y
。使用简写方式可以简化闭包表达式的书写方式,提高代码的可读性。
var array = [1, 2, 3]
array.sort(by: {(item1 : Int, item2: Int) -> Bool in return item1 < item2 })
print(array) // [1, 2, 3]
- 利用上下文推断参数和返回值类型
array.sort(by: {(item1, item2) -> Bool in return item1 < item2 })
array.sort(by: {(item1, item2) in return item1 < item2 })
- 单表达式可以隐士返回,既省略
return
关键字
array.sort{(item1, item2) in item1 < item2 }
- 参数名称的简写(比如
$0
)
array.sort{ return $0 < $1 }
array.sort{ $0 < $1 }
array.sort{ < }
闭包捕获值
闭包捕获值是指闭包在创建时,可以捕获外部函数的变量或参数,并在闭包内部使用这些变量或参数。
闭包捕获一个全局变量
我们首先来看一下闭包捕获全局变量的情况,代码如下
var i = 1
let closure = {
print(i) //输出 2
}
i += 1
print(i) //输出 2
closure()
print(i) //输出 3
可以看到,i
的值发生变化后,closure
里面的i
也发生了变化。和OC里面的block
很像。
当执行到closure
闭包的时候,直接去寻找变量i
的地址,然后把i
的值取出来。而此时i
的值已经发生了变化,因此取出来i
的值就是2。
闭包捕获一个局部变量
当闭包捕获一个局部变量时,内部又进行了哪些操作呢? 我们拿官方的例子验证一下。
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
let makeInc = makeIncrementer()
闭包捕获变量其实是在堆空间里面创建一个实例对象,并且把捕获变量的值存储到这个实例对象中,每次调用闭包使用的都是同一个堆空间的实例变量地址,所以在闭包外面修改值,闭包内部的值也会改变。
逃逸闭包
逃逸闭包是 Swift 中一种用于捕获外部变量或参数的闭包。逃逸闭包可以在函数内部创建,并在函数返回后继续执行。逃逸闭包可以用于捕获外部函数的变量或参数,并在闭包内部使用这些变量或参数。
在 Swift 中,可以使用闭包表达式来创建逃逸闭包。闭包表达式的语法类似于普通闭包,但在闭包内部可以使用 return
语句来返回逃逸闭包。例如:
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
let add = add(1, 2)
print(add) // 输出 3`
在上面的代码中,add
是一个函数,它接受两个参数 a
和 b
,并返回它们的和。函数 add
内部使用了闭包表达式来创建逃逸闭包 add
。逃逸闭包 add
可以捕获外部函数 add
的变量 a
和 b
,并在闭包内部使用这些变量。最后,逃逸闭包 add
返回的值是 a + b
,即 3
。
需要注意的是,逃逸闭包可以捕获外部函数的变量或参数,但这并不意味着逃逸闭包可以访问外部函数的返回值。如果外部函数没有返回值,那么逃逸闭包将无法访问外部函数的返回值。
非逃逸闭包
非逃逸闭包是 Swift 中的一种闭包特性,它可以防止闭包外部的变量或参数被闭包捕获并重用。在 Swift 中,非逃逸闭包通常用于捕获外部函数的变量或参数,并在闭包内部使用这些变量或参数,以防止这些变量或参数被重复捕获并重用,导致内存泄漏等问题。
在 Swift 中,可以使用非逃逸闭包来创建闭包表达式。非逃逸闭包的语法类似于普通闭包,但在闭包内部不能使用 return
语句来返回闭包。例如:
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
let add = add(1, 2)
print(add) // 输出 3
在上面的代码中,add
是一个函数,它接受两个参数 a
和 b
,并返回它们的和。函数 add
内部使用了闭包表达式来创建非逃逸闭包 add
。非逃逸闭包 add
可以捕获外部函数 add
的变量 a
和 b
,并在闭包内部使用这些变量。由于闭包内部不能使用 return
语句,因此非逃逸闭包 add
返回的值是 a + b
,即 3
。
需要注意的是,非逃逸闭包只能用于捕获外部函数的变量或参数,不能用于捕获外部函数的返回值。如果外部函数没有返回值,那么非逃逸闭包将无法访问外部函数的返回值。
自动闭包
@autoclosure
是一种自动创建的闭包,用于将参数包装成闭包。这种闭包不接受任何参数,当它被调用的时候,会返回传入的值。这种便利语法让你在调用的时候能够省略闭包的花括号
函数中有一个 ()-> Any
类型的参数,用@autoclosure
修饰时,调用函数的时候可以传入一个确定的值 a
,这个值会被自动包装成(){return a}
的闭包,就不需要显示的将闭包表达式写出来
func debugOutPrint(_ condition: Bool , _ message: @autoclosure () -> String){
if condition {
print("debug:(message())")
}
}
debugOutPrint(true,"Application Error Occured" )
debugOutPrint(true, getString )
func getString()->String{
return "Application Error Occured"
}
闭包的循环引用
Swift 有如下要求:只要在闭包内使用 self
的成员,就要用 self.someProperty
或者 self.someMethod()
(而不只是 someProperty
或 someMethod()
)。这提醒你可能会一不小心就捕获了 self
。
class ClassA {
var number : Int = 0
lazy var someClosure = {
[unowned self]
(index: Int) -> Void in
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
print(self.number)
}
print(self.number)
}
lazy var someClosure2 = {
[weak self]
index: Int) -> Void in
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
print(self?.number ?? 0)
}
print(self?.number ?? 0)
}
deinit {
print("ClassA---deinit")
}
}
var n : ClassA? = ClassA();
n?.someClosure(23)
n = nil
学习 Swift,勿忘初心,方得始终。但要陷入困境时,也不要忘了最初的梦想和时代所需要的技能。