Swift @escaping 和完成处理程序

Posted

技术标签:

【中文标题】Swift @escaping 和完成处理程序【英文标题】:Swift @escaping and Completion Handler 【发布时间】:2018-02-24 23:55:45 【问题描述】:

我正在尝试更准确地理解 Swift 的“关闭”。

但是@escapingCompletion 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

保存在全局变量completionHandlers 中的self.x = 100 中的self 如何连接到instanceSomeClass 对象?

其次,我是这样理解someFunctionWithEscapingClosure的。

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

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

@escaping 将闭包保存在内存中

是这样吗?

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

也许这是一个非常初级的问题。

如果我们希望某个函数在某个特定函数之后执行。为什么我们不在一个特定的函数调用之后调用某个函数呢?

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

【问题讨论】:

【参考方案1】:
/*the long story short is that @escaping means that don't terminate the function life time until the @escaping closure has finished execution in the opposite of nonEscaping closure the function can be terminated before the closure finishes execution Ex:
*/

 func fillData(completion: @escaping: () -> Void) 
     /// toDo 
    completion()
  

//___________________________

//The call for this function can be in either way's @escaping or nonEscaping :

    
fillData
 /// toDo

    

/* again the deference between the two is that the function can be terminated before finish of execution nonEscaping closure in the other hand the @escaping closure guarantees that the function execution will not be terminated before the end of @escaping closure execution. Hope that helps ***#(NOTE THAT THE CLOSURE CAN BE OF ANY SWIFT DATA TYPE EVEN IT CAN BE TYPEALIAS)*/
 

【讨论】:

"':'后面的预期参数类型"【参考方案2】:

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

正如 Bob Lee 在他的博文 Completion Handlers in Swift with Bob 中解释的那样:

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

那么,如何在下载完成后才运行一段代码 完全的?此外,您如何仅在 视图控制器已移至下一个?好吧,我们要找到 了解如何设计一个像老板一样的人。

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

事情做完了就做事

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

@escaping 闭包:

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

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

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

异步执行:当你在调度队列上异步执行闭包时,队列会为你将闭包保存在内存中,以备将来使用。在这种情况下,您不知道何时执行闭包。

当你尝试在这些场景中使用闭包时,Swift 编译器会显示错误:

要更清楚地了解此主题,您可以查看this post on Medium。

再补充一点,每个ios开发者都需要了解:

    转义闭包:转义闭包是在传递给返回的函数之后调用的闭包。换一种说法, 它比传递给它的函数寿命更长。 非转义闭包:在传入的函数中调用的闭包,即在它返回之前。

【讨论】:

@shabhakar,如果我们存储一个闭包但以后不调用它会怎样。或者如果方法被调用了两次,但我们只调用了一次闭包。因为我们知道结果是一样的。 @user1101733 我认为您在谈论转义闭包,在您不打电话之前,闭包不会执行。在上面的示例中,如果调用 doSomething 方法 2 次 2 completionHandler 对象将添加到 completionHandlers 数组中。如果您从 completionHandlers 数组中获取第一个对象并调用它将执行,但 completionHandlers 数组计数将保持不变(2)。 @Deepak,是的,关于逃生关闭。假设我们不使用数组并使用普通变量来存储闭包引用,因为我们要执行最近的调用。一些内存是否会被以前永远不会调用的闭包占用? @user1101733 闭包是引用类型(如类),当您为变量分配新的闭包时,属性/变量将指向新的闭包,因此 ARC 将为以前的闭包释放内存。【参考方案3】:

这是我用来提醒自己@escaping 是如何工作的一小部分示例。

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()
        
    


【讨论】:

此代码不正确。它缺少 @escaping 限定符。 我最喜欢这个i.e. escape the scope of this function.

以上是关于Swift @escaping 和完成处理程序的主要内容,如果未能解决你的问题,请参考以下文章

如何在 API 调用中设置完成处理程序 - Swift 5

为链式函数使用 Swift 完成处理程序

Swift:带有完成处理程序的多线程

完成处理程序中的 Swift 泛型

使用 Firebase 的 Swift 完成处理程序

Swift 4 将参数添加到 URLRequest 完成处理程序