Swift @转义和完成处理程序


98

我试图更准确地理解Swift的“关闭”。

但是@escaping而且Completion Handler太难理解了

我搜索了许多Swift帖子和官方文档,但感觉还不够。

这是官方文件的代码示例

var completionHandlers: [()->Void] = []

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

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

class SomeClass{
    var x:Int = 10
    func doSomething(){
        someFunctionWithEscapingClosure {
            self.x = 100
            //not excute yet
        }
        someFunctionWithNoneescapingClosure {
            x = 200
        }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)

completionHandlers.first?() 
print(instance.x)

我听说有两种使用方法和原因 @escaping

首先是用于存储闭包,其次是用于异步操作。

以下是我的问题

首先,如果doSomething执行,someFunctionWithEscapingClosure则将使用闭包参数执行,并且该闭包将保存在全局变量数组中。

我认为封包是{self.x = 100}

self保存在全局变量中的{self.x = 100}中如何completionHandlers连接到instance该对象SomeClass

其次,我这样理解someFunctionWithEscapingClosure

要将局部变量闭包存储completionHandler到全局变量'completionHandlers we using@ escaping`关键字!

没有@escaping关键字someFunctionWithEscapingClosure返回,局部变量completionHandler将从内存中删除

@escaping 是将闭包保留在内存中

这是正确的吗?

最后,我只是想知道这种语法的存在。

也许这是一个非常基本的问题。

如果我们希望某些功能在某些特定功能之后执行。为什么我们不只在特定的函数调用之后才调用某些函数?

使用上面的模式和使用转义的回调函数有什么区别?

Answers:


122

Swift完成处理程序转义和非转义:

正如鲍勃·李(Bob Lee)在他的博客文章《鲍勃·斯威夫特中完成处理程序》中所说的那样:

假设用户在使用时正在更新应用程序。您一定要在完成时通知用户。您可能需要弹出一个框,上面写着:“恭喜,您现在可以尽情享受了!”

那么,如何仅在下载完成后才运行代码块?此外,如何仅在将视图控制器移至下一个对象后才能对某些对象进行动画处理?好吧,我们将找出如何像老板一样设计一个人。

根据我广泛的词汇表,完成处理程序代表

做完事后做事

Bob的帖子清楚地说明了完成处理程序(从开发人员的角度来看,它准确定义了我们需要理解的内容)。

@转义闭包:

当在函数参数中传递闭包时,在函数体执行后使用闭包并返回编译器。当函数结束时,传递的闭包的作用域存在并存在于内存中,直到执行闭包为止。

有几种方法可以使包含函数转义闭包:

  • 存储:当需要将闭包存储在全局变量中时,将执行调用函数之后的内存中存在的属性或任何其他存储,然后将编译器返回。

  • 异步执行:在分派队列上异步执行闭包时,队列将为您将闭包保存在内存中,以后可以使用。在这种情况下,您不知道何时执行闭包。

在这些情况下尝试使用闭包时,Swift编译器将显示错误:

错误截图

有关此主题的更多信息,请查看Medium中的这篇文章

再增加一点,每个ios开发人员都需要了解:

  1. 转义闭包:转义闭包是在将函数传递给返回值之后调用的闭包。换句话说,它比传递给它的功能更持久。
  2. 非转义闭包:在传递给它的函数内(即返回之前)调用的闭包。

@shabhakar,如果我们存储一个闭包但以后不调用该怎么办。或者,如果方法被调用了两次,但是我们只调用了一次闭包。既然我们知道结果是一样的。
user1101733 '18

@ user1101733我认为您在谈论转义闭包,闭包在您不调用之前将不会执行。在上面的示例中,如果将doSomething方法调用2次,则2个completionHandler对象将添加到completeHandlers数组中。如果您从completionHandlers数组中获取第一个对象并调用它,则它将执行,但completionHandlers数组的计数将保持不变(2)。
迪帕克

@Deepak,是的,关于逃生关闭。假设我们不使用数组,而使用常规变量来存储闭包引用,因为我们要执行最新的调用。以前的闭包会占用一些内存,而这些闭包永远不会调用吗?
user1101733 '18

1
@ user1101733闭包是引用类型(如类),当您为变量分配新的闭包时,属性/变量将指向新的闭包,因此ARC将为先前的闭包分配内存。
迪帕克

28

这是我用来提醒自己@转义的工作方式的一小类示例。

class EscapingExamples: NSObject {

    var closure: (() -> Void)?

    func storageExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because `closure` is outside the scope of this
        //function - it's a class-instance level variable - and so it could be called by any other method at
        //any time, even after this function has completed. We need to tell `completion` that it may remain in memory, i.e. `escape` the scope of this
        //function.
        closure = completion
        //Run some function that may call `closure` at some point, but not necessary for the error to show up.
        //runOperation()
    }

    func asyncExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because the completion closure may be called at any time
        //due to the async nature of the call which precedes/encloses it.  We need to tell `completion` that it should
        //stay in memory, i.e.`escape` the scope of this function.
        DispatchQueue.global().async {
            completion()
        }
    }

    func asyncExample2(with completion: (() -> Void)) {
        //The same as the above method - the compiler sees the `@escaping` nature of the
        //closure required by `runAsyncTask()` and tells us we need to allow our own completion
        //closure to be @escaping too. `runAsyncTask`'s completion block will be retained in memory until
        //it is executed, so our completion closure must explicitly do the same.
        runAsyncTask {
            completion()
        }
    }





    func runAsyncTask(completion: @escaping (() -> Void)) {
        DispatchQueue.global().async {
            completion()
        }
    }

}

2
该代码不正确。它缺少@escaping预选赛。
罗布

我最喜欢这个i.e. escape the scope of this function.
Gal
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.