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 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)的形式输出消息。

微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?