上一章Swift教程请查看:swift函数重载
在本文中,你将通过示例了解Swift中什么是闭包、闭包使用语法和闭包类型。
在Swift函数中,我们使用func关键字创建了一个函数。然而,Swift中还有另一种特殊类型的函数,称为闭包,可以在不使用关键字func和函数名的情况下定义闭包。
与函数一样,闭包可以接受参数和返回值。它还包含一组语句,在你调用它之后执行,可以将其作为函数分配给变量/常量。
闭包的使用主要有两个原因:
- 完成模块
闭包帮助你在某个任务完成执行时得到通知。请将闭包视为完成处理程序,以了解有关它的更多信息。
- 高阶函数
闭包可以作为高阶函数的输入参数传递。高阶函数只是接受function作为输入并将函数的值作为输出返回的函数类型。
出于这个目的,最好使用闭包来替换函数,因为闭包省略了func关键字和函数名,这使代码更容易读和更短。
闭包的语法
闭包表达式语法的一般形式如下:
{ (parameters) -> return type in
statements
}
注意,在返回类型之后使用了in关键,。in关键字用于分隔闭包中的返回类型和语句,闭包接受参数并可以返回值。让我们学习创建自己的闭包,如下面的例子:
示例1:简单的闭包
let simpleClosure = {
}
simpleClosure()
在上面的语法中,我们声明了一个简单的闭包{},它不接受参数,不包含语句,也不返回值,这个闭包是被分配给常量的简单闭包。
我们将闭包称为simpleClosure(),但是由于它不包含任何语句,程序什么也不做。
例2:包含语句的闭包
let simpleClosure = {
print("Hello, World!")
}
simpleClosure()
在上面的程序中,你已经定义了一个闭包simpleClosure,simpleClosure的类型被推断为()->(),因为它不接受参数,也不返回值。
如果希望显式定义闭包的类型,可以这样做:let simpleClosure:() ->()。
与第一个示例不同,将闭包调用为simpleClosure()将执行其中的print()语句。
例3:接受参数的闭包
let simpleClosure:(String) -> () = { name in
print(name)
}
simpleClosure("Hello, World")
在上面的程序中,closure
(String)的类型->()声明闭包接受String类型的输入,但不返回值。你可以通过将参数名后跟in关键字来使用传递到闭包语句中的值。
请记住,in关键字的用法是将参数名与语句分隔开。因为闭包接受一个字符串,所以在调用闭包simpleClosure(“Hello, World”)时需要传递这个字符串。
例4:带返回值的闭包
闭包也可以作为函数返回值。如果需要从闭包返回值,则必须在大括号()中显式地添加要返回的类型,后面跟着->。
let simpleClosure:(String) -> (String) = { name in
let greeting = "Hello, World! " + "Program"
return greeting
}
let result = simpleClosure("Hello, World")
print(result)
在上面的程序中,我们将类型定义为simpleClosure: (String) -> (String),因为Swift不能自动推断返回值的闭包,type (String) -> (String)声明闭包接受String类型的输入并返回String类型的值。
闭包还返回一个使用return关键字作为返回查询的值,返回的值可以赋给一个变量/常量为 let result =,这是我们在Swift函数中学到的。
示例4:将闭包作为函数参数传递
我们也可以把闭包作为参数传递给一个函数:
func someSimpleFunction(someClosure:()->()) {
print("Function Called")
}
someSimpleFunction(someClosure: {
print("Hello World! from closure")
})
在上面的程序中,函数接受类型()->()的闭包,也就是不接受任何输入,也不返回任何值。
现在,当调用函数someSimpleFunction()时,你可以传递闭包{ print(“Hello World! from closure”) }作为一个参数。
函数调用在函数内部触发print(“function
Called”)语句,输出在屏幕上调用的函数。但是,由于没有调用闭包,所以没有执行闭包语句。
你可以简单地将闭包称为someClosure(),它在闭包内执行语句。
func someSimpleFunction(someClosure:()->()) {
print("Function Called")
someClosure()
}
someSimpleFunction(someClosure: {
print("Hello World! from closure")
})
后置闭包
如果一个函数接受一个闭包作为它的最后一个参数,那么这个闭包可以像函数体一样在{}之间传递,这种写在函数调用括号外的闭包称为后置闭包。
上面的程序可以用后置闭包重写为:
例5:后置闭包
func someSimpleFunction(msg:String, someClosure:()->()) {
print(msg)
someClosure()
}
someSimpleFunction(msg:"Hello Swift Community!") {
print("Hello World! from closure")
}
在上面的程序中,函数someSimpleFunction()接受闭包作为最终参数。因此,在调用函数时,我们没有将闭包作为参数传递,而是使用了后置闭包。
如你所见,在函数调用中
someSimpleFunction(msg:"Hello Swift Community!") {
print("Hello World! from closure")
}
看起来像是函数的主体而不是函数的参数,但是记住它仍然是函数的参数。
由于后置闭包的原因,我们没有为闭包指定参数名,这使得代码更短,更易读。
不强制编写后置闭包,但是建议在函数接受闭包作为最终参数时使用闭包,以提高可读性。
自动闭包
用@autoclosure关键字标记的闭包称为自动闭包,@autoclosure关键字通过添加{}来创建表达式周围的自动闭包。因此,在向函数传递闭包时可以省略大括号{}。
使用autoclosure的主要优点是,在调用闭包时不需要将表达式封装在大括号{}中。
让我们看看这个例子。
示例6:有@autoclosure的闭包
func someSimpleFunction(someClosure:()->(), msg:String) {
print(msg)
someClosure()
}
someSimpleFunction(someClosure: ({
print("Hello World! from closure")
}), msg:"Hello Swift Community!")
在上面的程序中,我们声明了一个函数,它接受一个正常的closure()->()作为函数someSimpleFunction()的参数。可以看到,在调用函数时,需要在函数参数周围添加{}如:
someClosure: ({
print("Hello World! from closure")
})
我们可以重写上述程序使用自动闭包为:
例7:自动闭包
func someSimpleFunction(someClosure: @autoclosure ()->(), msg:String) {
print(msg)
someClosure()
}
someSimpleFunction(someClosure: (print("Hello World! from closure")), msg:"Hello Swift Community!")
在上面的程序中,我们用@autoclosure属性将closure()->()标记为类型为autoclosure。因此,你不必在函数参数周围添加{}作为someClosure:(print())。
带有参数和返回值的自动闭包
与普通闭包一样,可以向自动闭包传递参数并从自动闭包返回值。但是,即使传递了参数,它们也会被忽略,不能在闭包中使用。这是因为你不能将参数定义为{arg in}。
因此,建议创建不接受参数但可以返回值的自动闭包,Value是封装在其中的表达式。让我们看看下面的例子。
例8:带返回值的自动闭包
func someSimpleFunction(_ someClosure:@autoclosure ()->(String)) {
let res = someClosure()
print(res)
}
someSimpleFunction("Good Morning")
在上面的程序中,我们定义了一个函数,它不接受参数,但是返回一个字符串(()->(String))。我们将自动闭包“Good Morning”传递给该函数,这也是闭包的返回值。
因此,当我们在函数中调用someClosure()时,它会返回值Good Morning。
例9:带参数的自动闭包
func someSimpleFunction(_ someClosure:@autoclosure (String)->(String)) {
let res = someClosure("Hello World")
print(res)
}
someSimpleFunction("Good Morning")
在上面的程序中,我们定义了一个接受自闭包的函数,闭包接受String类型的值,并返回String类型的值。
与前面的示例一样,我们将自动闭包“Good Morning”传递给函数,该函数是闭包的返回值。
因此,即使自动闭包被调用someClosure(“Hello
World”),它也不能接受任何参数。
转义与不可转义闭包
不可转义闭包
当闭包作为参数传递给函数并在函数返回之前调用时,闭包被认为是不可转义的,闭包不用于函数之外。
在Swift中,所有的闭包参数在默认情况下都是不可转义的。转义和不可转义闭包的概念是用于编译器优化的。
例10:不可转义的闭包
func testFunctionWithNoEscapingClosure(myClosure:() -> Void) {
print("function called")
myClosure()
return
}
//function call
testFunctionWithNoEscapingClosure {
print("closure called")
}
在上面的例子中,闭包被认为是不可转义的,因为在函数返回之前调用了闭包 myClosure(),并且闭包没有在函数体之外使用。
可转义闭包
当将闭包作为参数传递给函数时,闭包被称为转义函数,但是在函数返回或在函数体外部使用闭包时调用闭包。
示例11:转义闭包
var closureArr:[()->()] = []
func testFunctionWithEscapingClosure(myClosure:@escaping () -> Void) {
print("function called")
closureArr.append(myClosure)
myClosure()
return
}
testFunctionWithEscapingClosure {
print("closure called")
}
在上面的例子中,我们声明了一个变量closureArr,它可以存储类型为()->()的闭包数组。
现在,如果我们将在函数范围内定义的闭包myclosure追加到在函数外部定义的closureArr,则闭包myClosure需要避开函数体。
因此,闭包需要转义并使用@escaping关键字进行标记。
例12:不可转义闭包
在上面的不可转义闭包部分中,我们描述了如果在函数返回之前调用闭包,则闭包需要不可转义。所以,如果函数返回后调用闭包,那就应该转义?
让我们用一个例子来测试一下:
func testFunctionWithNoEscapingClosure(myClosure:() -> Void) {
return
myClosure()
}
上面的代码返回警告,因为出现在return语句之后的语句(在我们的例子中是myClosure())没有执行,因为return语句将程序的控制权转移给了函数调用者。
那么,我们如何测试以便在函数返回后调用闭包呢?如果将闭包调用放在异步操作中,则可以这样做。
同步操作等待操作完成/完成,然后移动到下一个语句(从上到下的顺序)。并且,异步移动到下一个语句,即使当前操作尚未完成。
因此,放置在异步操作中的语句可能会在稍后执行。
func testFunctionWithNoEscapingClosure(myClosure:@escaping () -> Void) {
DispatchQueue.main.async {
myClosure()
}
return
}
在上面的程序中DispatchQueue.main.sync运行异步的代码块,调用myClosure()函数返回后甚至可能发生,所以闭包需要转义,使用@escaping关键字标记。
闭包完成处理器
完成处理程序回调/通知,允许你执行一些操作,当一个函数完成了任务。
完成处理器主要应用于异步操作,调用者可以知道什么时候操作已完成执行一些操作完成后操作。
例13:闭包完成处理器
func doSomeWork(completion:()->()) {
print("function called")
print("some work here")
print("before calling callback")
completion()
print("after calling callback")
}
doSomeWork(completion: {
print("call back received")
})
print("Other statements")
上面的程序也可以用后置闭包重写为:
例14:后置闭包作为完成处理程序
func doSomeWork(completion:()->()) {
print("function called")
print("some work here")
print("before calling callback")
completion()
print("after calling callback")
}
doSomeWork() {
print("call back received")
}
print("Other statements")
完成处理器是如何工作的?
以下是执行步骤:
- doSomeWork()调用在函数内部执行语句的函数
- print输出在控制台中调用的函数。
- 你可以在函数内部执行一些工作,现在只是输出控制台中的一些工作的print(“some
work here”)语句。 - 对闭包的简单调用completion()将发送回调并将程序的控制权转移到闭包内的语句。
- 因此,print(“call back received”)执行,输出在控制台中接收到的回调。
- 之后,程序控制再次返回闭包调用,并执行语句print(“After
calling callback”),该语句在调用控制台中的回调后输出。 - 函数中的语句执行后,程序将控制转移到函数调用doSomeWork(),然后执行下一个语句print(“Other statements”),输出控制台中的其他语句。
- 让我们来看一个使用闭包作为完成处理程序的实际示例。
例11:闭包作为完成处理程序
var closeButtonPressed = false
func codeinPlayground(completion:(String)->()) {
print("Code, Take a Nap and Relax")
if closeButtonPressed {
completion("Close the playground")
}
}
codeinPlayground { (msg) in
print(msg)
}
在上面的程序中,我们已经声明了一个变量closeButtonPressed,如果用户点击了close按钮,它将模拟该变量。如果你按下关闭按钮,变量closeButtonPressed将是true。
函数codeinPlayground接受闭包作为参数,闭包完成接受一个字符串,但不返回值。由于closeButtonPressed被赋值为false,所以if语句中没有执行语句,也没有调用闭包。
这里,我们使用闭包作为完成处理程序,因为当用户点击关闭按钮时,我们不希望在codeinPlayground函数内执行语句,而是通过调用闭包
completion(“Close the playground”)来完成它的执行。
这样,我们就有机会处理与闭包语句中的函数相关的所有最终事件,在我们的例子中,我们在控制台中以print(msg)的形式输出消息。