完成处理程序是不是结束功能?

Posted

技术标签:

【中文标题】完成处理程序是不是结束功能?【英文标题】:Does a Completion Handler end the function?完成处理程序是否结束功能? 【发布时间】:2020-01-30 22:49:50 【问题描述】:

也许我不明白完成处理程序的概念,但我有一个这样的函数;

func someFunction(completion: @escaping (Bool, LoginError?) -> Void) 
    self.checkDevice()  allowed, error in
       if let e = error 
          completion(false, e)
       
        completion(true, nil)
    

虽然对checkDevice() 所做的事情很清楚,但基本前提是它执行异步网络调用,并返回 true 且没有错误 (nil),或返回 false 出现错误。

当我运行这段代码时,我发现完成处理程序被调用了两次。它以元组(false, error)和(true, nil)的形式发送完成。我已经进行了一些调试,似乎没有任何方式可以调用两次 someFunction()

我相信一旦发送了completion,函数就会结束。在我的测试用例中,我强制来自checkDevice() 的错误,这将导致我将完成发送为(false, error),但我同时看到(false, error)和(true, nil)。完成不会立即结束函数吗?

【问题讨论】:

不,它没有。完成处理程序只是一个您可以调用的闭包,被命名为“完成”并没有赋予它任何特殊属性 只需将第二个completion(true, nil) 调用放在ifelse 子句中即可。 【参考方案1】:

我相信一旦发送完成,函数就会结束。

不,为什么会这样?当你调用这个函数时,它就像调用任何其他函数一样。 名字没有魔力。

这样改写:

func someFunction(completion: @escaping (Bool, LoginError?) -> Void) 
    self.checkDevice()  allowed, error in
        if let e = error 
            completion(false, e)
            return // *
        
        completion(true, nil)
    

实际上,我应该提一下,这是编写完成处理程序的一种糟糕方式。与其采用两个参数,一个 Bool 和一个仅当 Bool 为 false 时才使用的可选 LoginError,它应该采用 one 参数 — 一个 Result,它携带我们成功或失败的两个参数,并且,如果我们失败了,错误是什么:

func someFunction(completion: @escaping (Result<Void, Error>) -> Void) 
    self.checkDevice()  allowed, error in
        completion( Result 
            if let e = error  throw e 
        )
    

如您所见,使用 Result 作为参数可以让您更优雅地响应所发生的事情。

确实,checkDevice 本身可以将 Result 传递给 完成处理程序,然后事情会更加优雅(和简单)。

【讨论】:

感谢您的回复。我很欣赏细节。在基于团队的环境中是否有遵循这种心态的最佳实践? IE。对于任何新的开发人员来说,Result 现在是另一个说开发人员必须遵循和理解的类。与 Bool 元组相比,LoginError(可能被认为是错误)对于不熟悉代码的人来说会更清楚,不是吗? 在我看来,结果更加清晰。我们有 one 参数,而不是必须猜测其含义的两个参数,一个枚举,其 case 为 .success.failure(以及一个有效负载,例如失败的情况)。还有什么比这更清楚的呢?从结果中提取该信息非常优雅和简单,比测试 Optional 的 nil 或 Bool 的 true 好得多。 谢谢,@matt。我已经开始与一个团队合作,并试图了解“最佳实践”。我觉得我创建的自定义类越多,我为团队创建的工作就越多,因为他们必须深入研究这些类的含义。但是我正在处理的项目有许多自定义类,所以我理解你提供的观点。谢谢! 我已经审查并实施了您的建议,并且确实看到了清晰度上的差异。感谢您分享这个。这是一个重要的学习课程和帮助!【参考方案2】:

完成处理程序不会结束函数。结束函数的典型方法是插入return

查看评论:您的具体情况

【讨论】:

在您的情况下,您的 self.checkDevice() 可能会多次返回,以便您的完成块被多次命中。我的怀疑是你可能在 self.checkDevice 函数中有一些东西,假设完成处理程序结束了函数。 这就回答了这个问题!非常感谢!【参考方案3】:

考虑包括参数名称(供参考)。并且完成应该只被调用一次。您可以使用return 或使用完整的if-else 条件来做到这一点。

func someFunction(completion: @escaping (_ done: Bool, _ error: LoginError?) -> Void) 

    checkDevice()  (allowed, error) in

        if let e = error 
            completion(false, e)
         else 
            completion(true, nil)
        

    



func someFunction(completion: @escaping (_ done: Bool, _ error: LoginError?) -> Void) 

    checkDevice()  (allowed, error) in

        if let e = error 

            completion(false, e)
            return

        

        completion(true, nil)

    


通过给参数命名,调用函数时,现在可以引用签名来表示其参数的含义:

someFunction  (done, error) in

    if let error = error 
        ...
     else if done 
        ...
    


【讨论】:

谢谢,@bsod。我很欣赏这方面的细节!

以上是关于完成处理程序是不是结束功能?的主要内容,如果未能解决你的问题,请参考以下文章

20190911实验报告结束

用c/c++/批处理 实现如下功能, 运行指定程序,在程序结束后,输出其运行时间和内存占用大小

如何使用完成处理程序和参数调用函数

需要在我的应用中实现“通话结束”功能

在锁定屏幕上检测播放结束 - AVPlayer

CCBPM工作流引擎,如下方式完成对流程的结束大总结