📜  Swift 闭包

📅  最后修改于: 2020-10-09 15:46:41             🧑  作者: Mango

在本文中,您将通过示例学习什么是闭包,语法,Swift中的闭包类型。

 

在Swift函数一文中,我们使用func关键字创建了一个函数 。但是,Swift中还有另一种特殊的函数类型,称为闭包 ,无需使用关键字func和函数名即可定义闭包

像函数一样,闭包可以接受参数并返回值。它还包含一组语句,这些语句在调用后执行,并且可以作为函数分配给变量/常量。

使用闭包主要有两个原因:

  1. 完成区块
    当某些任务完成执行时,闭包可帮助您得到通知。请参阅Closure作为完成处理程序以了解有关它的更多信息。
  2. 高阶函数
    闭包可以作为高阶函数的输入参数传递。高阶函数仅仅是一个类型的函数 ,其接受函数作为输入,并返回型函数作为输出的值。
    为此,最好使用闭包代替函数,因为闭包会省略func关键字和函数名,从而使代码更易读和简短。

闭包的语法

闭包表达式语法具有以下一般形式:

{ (parameters) -> return type in
   statements
}

请注意, in返回类型之后使用in关键字。 in关键字用于在闭包内部分隔返回类型和语句。闭包接受参数并可以返回值。让我们通过以下示例学习创建自己的闭包:


示例1:简单关闭

let simpleClosure = {   
}
simpleClosure()

在以上语法中,我们声明了一个简单的闭包{ } ,该闭包不带任何参数,不包含任何语句且不返回任何值。该闭包分配给常量simpleClosure

我们将闭包称为simpleClosure() ,但是由于它不包含任何语句,因此该程序不执行任何操作。


示例2:包含语句的闭包

let simpleClosure = {
    print("Hello, World!")
}
simpleClosure()

当您运行上述程序时,输出将是:

Hello, World!

在上面的程序中,您已经定义了一个关闭方式simpleClosure 。可以将simpleClosure的类型推断为() -> ()因为它不接受参数并且不返回值。

如果要显式定义闭包的类型,则可以使用let simpleClosure:() -> ()

与第一个示例不同,将闭包作为simpleClosure()调用会在其中执行print()语句。此打印Hello, World!在控制台中。


示例3:接受参数的闭包

let simpleClosure:(String) -> () = { name in
    print(name)
}
simpleClosure("Hello, World")

当您运行上述程序时,输出将是:

Hello, World!

在上面的程序中,闭包的类型(String) -> ()声明闭包接受的是String类型的输入,但不返回值。您可以通过在闭包的语句中放置参数名称name来使用传递的值其次是in关键字。

请记住, in关键字的使用是将参数名称与语句分开。由于闭包接受String ,因此您在将闭包称为simpleClosure("Hello, World")需要传递字符串 。这将在闭包内部执行语句并输出Hello,World!在控制台中。


示例4:返回值的闭包

闭包也可以作为函数返回值。如果需要从闭包中返回值,则必须显式添加类型以在大括号()返回,其后是->

let simpleClosure:(String) -> (String) = { name in
    
    let greeting = "Hello, World! " + "Program"
    return greeting
}

let result = simpleClosure("Hello, World")
print(result)

当您运行上述程序时,输出将是:

Hello, World! Program

在上面的程序中,我们将类型定义为simpleClosure: (String) -> (String)因为Swift无法自动推断出返回值的闭包。类型(String) -> (String)声明闭包接受String类型的输入,并返回String类型的值。

闭包还使用return关键字作为return greeting返回一个值,并且可以在Swift函数中了解到,返回值可以以let result =形式分配给变量/常量。


示例4:将闭包作为函数参数传递

我们还可以将闭包作为参数传递给函数,如下所示:

func someSimpleFunction(someClosure:()->()) {

    print("Function Called")
}

someSimpleFunction(someClosure: {
    print("Hello World! from closure")
})

当您运行上述程序时,输出将是:

Function Called

在上面的程序中,该函数接受类型为()->()的闭包,即不接受任何输入且不返回任何值。

现在,当调用函数 someSimpleFunction() ,可以将闭包{ 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")

})

当您运行上述程序时,输出将是:

Function Called
Hello World! from closure

尾随闭包

如果函数接受闭包作为最后一个参数,则可以类似于{ }之间的函数主体传递闭包。这种写在函数调用括号之外的闭包称为尾随闭包。

可以使用结尾闭包将上述程序重写为:

示例5:尾随闭包

func someSimpleFunction(msg:String, someClosure:()->()) {
    print(msg)
    someClosure()
}

someSimpleFunction(msg:"Hello Swift Community!")  {
    print("Hello World! from closure")
}

当您运行上述程序时,输出将是:

Hello Swift Community!
Hello World! from closure

在上面的程序中, 函数 someSimpleFunction()接受闭包作为最终参数。因此,在调用函数,我们没有使用闭包作为参数,而是将闭包作为参数传递。

如您所见,在函数调用中

someSimpleFunction(msg:"Hello Swift Community!")  {
    print("Hello World! from closure")
}

闭包{ print("Hello World! from closure") }看起来像函数的主体,而不是函数的参数,但是请记住,它仍然是函数的参数。

由于尾随闭包,我们没有为闭包指定参数名称,这会使代码更短,更易读。

写尾随闭包不是强制性的。但是,出于可读性考虑,建议当函数接受闭包作为最终参数时。


自动关闭

@autoclosure关键字标记的闭包称为自动闭包。 @autoclosure关键字通过添加{}在表达式周围创建自动关闭。因此,在将闭包传递给函数时,可以省略花括号{}

使用自动闭包的主要优点是,在调用闭包时,无需将表达式用大括号{}括起来。

让我们在示例中看一下。

示例6:不使用@autoclosure进行关闭

func someSimpleFunction(someClosure:()->(), msg:String) {
    print(msg)
    someClosure()
}

someSimpleFunction(someClosure: ({
    
    print("Hello World! from closure")

}), msg:"Hello Swift Community!")

当您运行上述程序时,输出将是:

Hello Swift Community!
Hello World! from 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!")

当您运行上述程序时,输出将是:

Hello Swift Community!
Hello World! from closure

 

在上面的程序中,我们已使用@autoclosure属性将闭包()->()标记为autoclosure类型。因此,您不必在函数参数周围添加{ }作为someClosure: (print("Hello World! from closure"))


具有参数和返回值的自动闭合

像普通的闭包一样,您可以将参数传递给自动闭包并从中返回值。但是,即使您传递参数,它们也会被忽略并且不能在闭包内部使用。这是因为您无法定义参数以将其用作{ arg in }

因此,建议创建不带参数但可以返回值的自动关闭。值是包装在其中的表达式。让我们在下面的示例中看到这一点。

示例8:具有返回值的自动闭合

func someSimpleFunction(_ someClosure:@autoclosure ()->(String)) {
    let res = someClosure()
    print(res)
}
someSimpleFunction("Good Morning")

当您运行上述程序时,输出将是:

Good Morning

在上面的程序中,我们定义了一个不带参数但返回String ( ()->(String) )的函数 。我们将自动关闭功能“早安”传递给了该函数。这也是闭包的返回值。

因此,当我们在函数内部调用someClosure() ,它返回了值Good Morning

示例9:带参数的自动闭合

func someSimpleFunction(_ someClosure:@autoclosure (String)->(String)) {
    let res = someClosure("Hello World")
    print(res)
}
someSimpleFunction("Good Morning")

当您运行上述程序时,输出将是:

Good Morning

在上面的程序中,我们定义了一个需要自动关闭的函数 。该闭包采用String类型的值,并且还返回String类型的值。

像前面的示例一样,我们将自动闭包“ Good Morning”传递给函数,这是闭包的返回值。

因此,即使自动someClosure("Hello World")被称为someClosure("Hello World") ,它也不能接受任何参数,它仍然返回并打印Good Morning


转义与否转义闭包

无逃逸关闭

当闭包作为参数传递给函数时,闭包被认为是无法逃避的,并且在函数返回之前被调用。闭包不在函数外部使用。

在Swift中,默认情况下所有闭包参数都不会转义。转义和不转义闭包的概念是用于编译器优化的。

示例10:没有转义的闭包
func testFunctionWithNoEscapingClosure(myClosure:() -> Void) {
    print("function called")
    myClosure()
    return
}


//function call
testFunctionWithNoEscapingClosure {
    print("closure called")
}

当您运行上述程序时,输出将是:

function called
closure called

在上面的例子中,封闭被说成是不逸出,因为封闭件myClosure()是函数返回前调用和闭合不函数的身体之外使用。


逃逸关闭

当闭包作为函数的参数传递给闭包时,闭包被认为是对函数的转义,但是在函数返回或在函数主体之外使用闭包后才调用闭包。

示例11:转义闭包
var closureArr:[()->()] = []
func testFunctionWithEscapingClosure(myClosure:@escaping () -> Void) {
    print("function called")

    closureArr.append(myClosure)
    myClosure()

    return
}

testFunctionWithEscapingClosure {
     print("closure called")
}

当您运行上述程序时,输出将是:

function called
closure called

在上面的示例中,我们声明了一个变量closureArr ,它可以存储类型为()->()的闭包的数组。

现在,如果我们追加函数的closureArr的范围内所述的封闭myClosure定义的函数之外,封myClosure需求逃脱函数体。

因此,闭包需要转义并用@escaping关键字标记。

示例12:没有转义的闭包

在上面的“不转义闭包”部分中,我们描述了如果在函数返回之前调用了闭包,则闭包需要不转义。因此,如果在函数返回后调用闭包,则应该转义,对吗?

让我们用一个例子来测试一下:

func testFunctionWithNoEscapingClosure(myClosure:() -> Void) {
    return
    myClosure()
}

上面的代码返回警告,因为出现在return语句之后的语句(在我们的示例中为myClosure() )没有执行,因为return语句将程序的控制权转移给了函数调用者。

因此,我们如何测试,以便在函数返回后调用闭包。如果将闭包调用放在异步操作内,则可以完成此操作。

同步操作在移至下一条语句(从上到下的顺序)之前等待操作完成/完成。并且,即使当前操作尚未完成,异步也会移至下一条语句。

因此,放置在异步操作内的语句可能稍后会在某些时候执行。

func testFunctionWithNoEscapingClosure(myClosure:@escaping () -> Void) {
    DispatchQueue.main.async {
        myClosure()
    }
    return
}

在上面的程序中, DispatchQueue.main.async异步运行代码块。所以,现在。即使函数返回后,闭合调用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")

当您运行上述程序时,输出将是:

function called
some work here
before calling callback
call back received
after calling callback
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")

完成处理程序如何工作?

Closure as a completion handler

执行步骤如下:

  1. doSomeWork()调用在函数内部执行语句的函数
  2. print("function called")输出在控制台中调用函数
  3. 您可以在函数内部执行一些工作。现在,仅是一条print("some work here")语句,它会在控制台中输出一些工作
  4. completion()一个简单调用,即completion()将发送回调并将程序的控制权转移到闭合内的语句。
    因此,执行print("call back received") ,输出在控制台中接收到的回呼
  5. 此后,程序控件再次返回到闭包调用,并执行语句print("after calling callback") ,该语句在控制台中调用回调后输出。
  6. 函数内部的语句执行后,程序将控件转移到函数调用doSomeWork() ,然后执行下doSomeWork()语句print("Other statements") ,该语句在控制台中输出Other语句

让我们看一个更实际的示例,将闭包用作完成处理程序。


示例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)
}

当您运行上述程序时,输出将是:

Code, Take a Nap and Relax
In the above program, we have declared a variable closeButtonPressed that mocks if user has tapped close button on playground. Think if you press the close button, variable closeButtonPressed will be true.

The function codeinPlayground accepts a closure as an argument. The closure completion accepts a String but does not return a value. Since the closeButtonPressed is assigned false, statement inside if statement does not execute and closure is not called.

Now, if you assign the closeButtonPressed to true (i.e., when user pressed close button) as var closeButtonPressed = true, statements inside if executes and closure is called.

When you assign true to variable closeButtonPressed, the output will be:


Code, Take a Nap and Relax
Close the playground


Here, we have used closure as a completion handler because when user taps the close button, we don't want to execute statements inside the function codeinPlayground, instead complete its execution by calling the closure completion("Close the playground").

This way we get a chance to handle all the final events related to the function inside the statements of the closure. In our case we have output the message in console as print(msg).