swift闭包函数高级编程 – Swift最新教程

上一章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 inlet 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”)来完成它的执行。
【swift闭包函数高级编程 – Swift最新教程】这样,我们就有机会处理与闭包语句中的函数相关的所有最终事件,在我们的例子中,我们在控制台中以print(msg)的形式输出消息。

    推荐阅读