Swift中的逃逸闭包

r8xiu3jd  于 2023-01-29  发布在  Swift
关注(0)|答案(8)|浏览(191)

我是Swift的新手,我在阅读手册时遇到了转义闭包。我根本没有得到手册的描述。有人能简单地给我解释一下Swift中的转义闭包是什么吗?

wnvonmuf

wnvonmuf1#

请看这个类:

class A {
    var closure: (() -> Void)?
    func someMethod(closure: @escaping () -> Void) {
        self.closure = closure
    }
}

someMethod将传入的闭包赋给类中的属性。
现在又来了一堂课:

class B {
    var number = 0
    var a: A = A()
    func anotherMethod() {
        a.someMethod { self.number = 10 }
    }
}

如果我调用anotherMethod,闭包{ self.number = 10 }将被存储在A的示例中,因为self是在闭包中捕获的,所以A的示例也将持有对它的强引用。
这基本上就是一个转义闭包的例子!
你可能想知道,“什么?那么封闭从哪里逃出来,逃到哪里去了?”
闭包从方法的作用域逃逸到类的作用域。并且它可以在以后调用,甚至在另一个线程上调用!如果处理不当,这可能会导致问题。
默认情况下,Swift不允许闭包转义,你必须在闭包类型中添加@escaping来告诉编译器“请允许这个闭包转义”。如果我们删除@escaping

class A {
    var closure: (() -> Void)?
    func someMethod(closure: () -> Void) {
    }
}

并尝试编写self.closure = closure,它无法编译!

7cjasjjr

7cjasjjr2#

我将用一种更简单的方式。
请看这个例子:

func testFunctionWithNonescapingClosure(closure:() -> Void) {
        closure()
}

以上是一个非转义闭包,因为闭包是在方法返回之前调用的。
考虑具有异步操作的相同示例:

func testFunctionWithEscapingClosure(closure:@escaping () -> Void) {
      DispatchQueue.main.async {
           closure()
      }
 }

上面的示例包含一个转义闭包,因为闭包调用可能发生在函数由于异步操作而返回之后。

var completionHandlers: [() -> Void] = []
 func testFunctionWithEscapingClosure(closure: @escaping () -> Void) {
      completionHandlers.append(closure)
 }

在上面的例子中,你可以很容易地意识到闭包是在函数体之外移动的,所以它需要是一个转义闭包。

  • 在Swift 3中添加了转义和非转义闭包以优化编译器。您可以搜索nonescaping闭包的优点。*
gab6jxml

gab6jxml3#

I find this website very helpful on that matter简单解释如下:
如果闭包作为参数传递给函数,并且在函数返回后被调用,则闭包将转义。
阅读更多的链接,我通过以上!:)

fxnxkyjh

fxnxkyjh4#

默认情况下,闭包是非转义的。为了便于理解,你可以把非转义闭包看作局部闭包(就像局部变量一样),把转义闭包看作全局闭包(就像全局变量一样)。这意味着一旦我们从方法体中出来,非转义闭包的作用域就丢失了。但是在转义闭包的情况下,内存将闭包保留在内存中。

***简单地说,当我们在方法中的任何异步任务内调用闭包时,或者在调用闭包之前方法返回时,我们使用转义闭包。

    • 非转义闭包(_E):-**
func add(num1: Int, num2: Int, completion: ((Int) -> (Void))) -> Int {
    DispatchQueue.global(qos: .background).async {
        print("Background")
        completion(num1 + num2) // Error: Closure use of non-escaping parameter 'completion' may allow it to escape
    }
    return num1
}

override func viewDidLoad() {
    super.viewDidLoad()
    let ans = add(num1: 12, num2: 22, completion: { (number) in
        print("Inside Closure")
        print(number)
    })
    print("Ans = \(ans)")
    initialSetup()
}

因为它是非转义闭包,一旦我们从"add"方法中出来,它的作用域将丢失。completion(num1 + num2)将永远不会调用。

    • 逃逸关闭:-**
func add(num1: Int, num2: Int, completion: @escaping((Int) -> (Void))) -> Int {
    DispatchQueue.global(qos: .background).async {
        print("Background")
        completion(num1 + num2)
    }
    return num1
}

即使方法返回(即我们离开方法作用域),闭包也会被调用。enter code here

vxbzzdmp

vxbzzdmp5#

斯威夫特4.1

来自语言参考:Swift编程语言的属性(Swift 4.1)
苹果清楚地解释了escaping属性。
将此特性应用于方法或函数声明中的参数类型,以指示可以存储该参数的值以供以后执行。这意味着允许该值在调用的生存期之后继续存在。具有转义类型特性的函数类型参数要求对属性或方法显式使用self.。有关如何使用转义特性的示例,请参见Escaping Closures

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

someFunctionWithEscapingClosure(_:)函数将闭包作为参数,并将其添加到在函数外部声明的数组中。如果不使用@escaping标记此函数的参数,则会出现编译时错误。
当闭包作为参数传递给函数,但在函数返回后被调用时,闭包被称为转义函数。当声明一个将闭包作为其参数之一的函数时,可以在参数类型之前写入@escaping,以指示允许该闭包转义。

pbwdgjma

pbwdgjma6#

逃逸的定义

Swift的闭包是引用类型,这意味着如果你将两个变量指向同一个闭包,它们共享这个闭包-- Swift只需通过增加引用计数来记住有两个东西依赖于它。
当一个闭包被传递给一个函数使用时,Swift需要知道这个函数是否会被立即使用,或者是否会被保存以供以后使用。编译器可以跳过对引用计数加1,因为闭包会被直接运行,然后被遗忘,但是如果它被稍后使用--或者甚至可能被稍后使用--Swift需要给它的引用计数加一,这样它就不会意外地被销毁。

快速示例

转义闭包的一个很好的例子是完成处理程序,它在将来执行,当一个冗长的任务完成时,所以它的寿命比创建它的函数的寿命长。另一个例子是异步编程:异步执行的闭包总是会脱离它的初始上下文。

public func responseData(
    queue: DispatchQueue? = nil,
    completionHandler: @escaping (DataResponse<Data>) -> Void)
    -> Self
{
    ...

附加信息

由于性能原因,Swift假设所有闭包都是非转义闭包,这意味着它们将在函数内部立即使用,而不会被存储,这反过来意味着Swift不触及引用计数,如果不是这样--如果你采取任何措施来存储闭包--Swift将强制你将其标记为@escaping,这样引用计数就必须改变。

8hhllhi2

8hhllhi27#

    • 非转义(@noescape)与转义(@escape)闭包**

Function and closure(https://stackoverflow.com/a/60988242/4770877)

一个月一个月

@noescape是传递给函数的闭包,在函数返回之前调用
non-escaping closure的一个很好的例子是Arraysort function-sorted(by: (Element, Element) -> Bool)。这个闭包在执行排序计算时被调用。
历史记录:@noescapeSwift 2中被引入-〉在Swift 3中被弃用,成为默认值,这就是为什么您应该显式标记@escaping属性。

func foo(_ nonEscapingClosure: () -> Void) {
    nonEscapingClosure()
}

escaping closure

转义闭包(引用)在方法结束时仍处于活动状态。

//If you have next error
Escaping closure captures non-escaping parameter
//`@escaping` to the rescue

@escaping是一个闭包,
1.传入函数

  1. owner函数将这个闭包保存在属性中
    1.在所有者函数返回**之后调用闭包(使用属性)(异步)
    异步操作中的completion handler就是escaping closure的一个很好的示例。在这种情况下,如果不使用@escaping标记函数,则会出现编译时错误。引用escaping closure中的属性要求显式使用self
class MyClass {
    var completionHandler: (() -> Void)?

    func foo(_ escapingClosure: @escaping () -> Void) {
        
        //if you don't mark as @escaping you get
        //Assigning non-escaping parameter 'escapingClosure' to an @escaping closure
        completionHandler = escapingClosure //<- error here
    }

    func onEvent() {
        completionHandler?()
    }
}

completioncompletionHandlerAbout,如果您想突出显示它是@escaping,则可以使用completionHandler命名
一个三个三个一个
Sync vs Async(https://stackoverflow.com/a/75225609/4770877)
Java functional interfaces(https://stackoverflow.com/a/61232621/4770877)

6qftjkof

6qftjkof8#

下面是转义和无转义闭包的简单示例。

    • 无刮擦封口**
class NonEscapingClosure {

func performAddition() {
    print("Process3")
    addition(10, 10) { result in
        print("Process4")
        print(result)
    }
    print("Process5")
}

func addition(_ a: Int, _ b: Int, completion: (_ result: Int) -> Void) {
    print("Process1")
    completion(a + b)
    print("Process2")
}}
    • 创建示例并调用函数调用**
let instance = NonEscapingClosure()
    instance.performAddition()
    • 输出:**
Process3
 Process1
 Process4
 20
 Process2
 Process5
    • 和逃脱的结束**
class EscapingClosure {

func performAddition() {
    print("Process4")
    addition(10, 10) { result in
        print(result)
    }
    print("Process5")
}

func addition(_ a: Int, _ b: Int, completion: @escaping (_ result: Int) -> Void) {
    print("Process1")
    let add = a + b
    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        print("Process2")
        completion(add)
    }
    print("Process3")
}}
    • 创建示例和函数调用**
let instance = EscapingClosure()
    instance.performAddition()
    • 产出**
Process4
 Process1
 Process3
 Process5
 Process2
 20

相关问题