在Swift中,我正在学习@escaping
方法的返回类型,我知道它是用于异步调用的。问题是:我们是否需要确保completionHandler
在所有代码路径中被处理?请考虑以下代码示例:
func getData(){
testEscaping { data in
print("I get the data")
}
}
func testEscaping(completionHandler: @escaping (_ data: Data) -> ()) {
return;
}
print方法似乎会卡住,因为testEscaping
方法中从未调用completionHandler
。这是一个问题还是应该没事?
最初的想法是上面的代码是否有一些内存泄漏问题。为什么编译器不警告我?换句话说,我们是否需要非常小心地确保在使用escapting
时在所有代码路径中调用completionHandler
?如果代码逻辑很复杂,我们应该如何找到丢失的completionHandler
?
func testEscaping(completionHandler: @escaping (_ data: Data) -> ()) {
guard { /* ... */ } else {
// easy to know to call completionHandler
completionHandler(nil)
return
}
// ... some complex logic which might cause exceptions and fail at the middle
// ... should we catch all possbile errors and call completionHandler or it should OK
// ... miss the completionHandler and throw the error out?
completionHandler(goodData)
}
2条答案
按热度按时间brvekthn1#
我们是否需要确保completionHandler在所有代码路径中都被处理?
对于逃逸闭包,没有。
对于异步方法中的完成处理程序,可能是的。
转义闭包不一定用于异步任务。它仅仅表明闭包的生存期可能超过被调用方的生存期。它可以存储为被调用方的属性,一些全局变量等。由于它本身与异步任务无关,因此对未处理的转义闭包发出警告是没有意义的。我们甚至不知道哪些转义闭包是完成处理程序!
当涉及到带有完成处理程序的异步方法时,在每个可能的执行路径上调用一次且仅一次可能是一个好主意,因为这就是Swift Concurrency
async
方法的工作方式。如果你开始使用新的并发特性,并将现有的基于完成的异步方法移植到async
方法中,多次调用它将导致崩溃(假设你使用的是CheckedContinuation
),而不调用它将导致Task
闭包和它捕获的变量泄漏。最初的想法是上面的代码是否有一些内存泄漏问题。
由于
@escaping
表明闭包 * 可能 * 比上下文更持久,因此它不会泄漏任何内容,除非您实际上使其更持久并以某种方式制造泄漏的原因。在您提供的示例中,闭包在testEscaping
执行完成后没有对其的引用,因此它会立即被释放。如果代码逻辑很复杂,我们应该如何找到缺少的completionHandler?
这个问题没有简单的答案。
defer
可能会有所帮助,但最终还是要由实现者来决定。这也是Swift提出async/await概念的原因之一。
通过简单地返回而不调用正确的完成处理程序块,可以很容易地提前退出异步操作。如果忘记了,这个问题很难调试-https://github.com/apple/swift-evolution/blob/main/proposals/0296-async-await.md#problem-4-many-mistakes-are-easy-to-make
使用新的async方法,异步任务的完成通过返回方法来表示。就像在同步方法中不能错过返回一样,在异步方法中也不能错过返回。
s2j5cfk02#
为了回答您的问题,根据您的用例,如果您的
testEscaping
方法没有在所有路径上调用完成处理程序,那么情况可能会很糟糕。假设它是一个异步方法,调用者可能永远不会收到testEscaping
完成的通知。我个人使用
defer
语句来处理这类事情。defer
将确保某些代码将在范围的末尾运行(例如方法,闭包,do块,循环等)。下面是一个例子:
注意我是如何定义一个
Error
枚举的,它可以与Swift内置的Result
类型沿着使用。这样,如果出现问题,我们可以向呼叫者传递更多有用的信息。完成处理程序只在两个地方被调用。就在
isReady
条件语句之后和defer
语句内部。我这样做是为了说明defer
语句只有在定义了defer
语句之后控制到达作用域末尾时才会执行。如果控制到达第一个guard语句内的作用域末尾,则不会执行
defer
语句。这是因为guard语句在defer
语句之前定义。希望这能帮上忙。