闭包与委托模式

Closures vs Delegate pattern

我正在处理网络请求classes,我担心崩溃。例如,当您将回调方法传递给函数时,使用闭包非常容易:

// some network client
func executeHttpRequest(#callback: (success: Bool) -> Void) {
    // http request

    callback(true)
}

// View Controller
func reload() {
    networkClient.executeHttpRequest() { (success) -> Void in
        self.myLabel.text = "it succeeded" // NOTE THIS CALL
    }
}

但是,由于应该执行回调的进程是异步的,当回调与容器 class 元素(在本例中为 UIKit class)交互时,它可能容易受到在

等情况下崩溃
  1. 用户在异步任务仍在执行时导航到另一个视图控制器
  2. 用户在异步任务仍在执行时按下了主页按钮
  3. 等等...

因此,当回调最终被触发时,self.myLabel.text 可能会导致崩溃,因为 self 所引用的视图控制器可能已经被释放。

到此为止。我是对的,还是swift在内部实施了一些东西,这样就不会发生这种情况?

如果我是对的,那么这就是委托模式派上用场的时候了,因为委托变量是 weak references,这意味着,如果释放它们,它们不会保留在内存中。

// some network client

// NOTE this variable is an OPTIONAL and it's also a WEAK REFERENCE
weak var delegate: NetworkClientDelegate?

func executeHttpRequest() {
    // http request

    if let delegate = self.delegate {
        delegate.callback(success: true)
    }
}

注意 self.delegate,因为它是 weak reference,如果视图控制器(实现 NetworkClientDelegate 协议的)被释放,它将指向 nil,并且在这种情况下不会调用回调。

我的问题是:闭包是否有任何特殊之处使它们成为类似于此场景的不错选择,而不是回到委托模式?如果提供闭包示例(不会因 nil 指针而导致崩溃),那将是很好的。谢谢。

如果我是你,我会使用闭包,因为在这种情况下闭包比委托更方便、更灵活。

关于用户导航到其他视图控制器和仍在后台执行的异步操作,您可以保留对这些操作的引用,并且每当用户导航离开视图控制器时,您都可以取消它们。

或者,您可以在更新 UI:

之前验证视图控制器的视图是否可见
viewController.isViewLoaded && viewController.view.window

关于输入 background/foreground 的应用程序,您可以 pause/resume 通过覆盖 applicationDidEnterBackgroundapplicationWillEnterForeground 或注册适当的通知来进行操作:UIApplicationWillEnterForegroundNotification / UIApplicationDidEnterBackgroundNotification

我强烈建议您使用 AFNetworking,因为它 API 提供了我上面提到的所有内容,还有更多。

So, when the callback finally gets fired, self.myLabel.text might result in a crash, as the View Controller to whom self was referring could already be deallocated.

如果 self 已作为强引用导入到闭包中,则保证 self 不会 被释放,直到闭包被释放执行完毕。也就是说,当调用闭包时视图控制器仍然存在——即使此时它的视图不可见。语句self.myLabel.text = "it succeeded"会被执行,但即使标签不可见,也不会crash.

不过,有一个微妙的问题可能会在某些情况下导致崩溃:

假设,闭包具有对视图控制器的最后也是唯一的强引用。闭包完成,随后被释放,这也释放了对视图控制器的最后一个强引用。这必然会调用view controller的dealloc方法。 dealloc 方法将在执行闭包的同一线程上执行。现在,视图控制器是一个 UIKit 对象,必须保证发送到该对象的所有方法都将在 主线程 上执行。因此,IFF dealloc 将实际在其他线程上执行,您的代码可能会崩溃。

一个合适的方法需要 "cancel" 一个异步任务,当它是 "closed" 时,视图控制器不再需要其结果。当然,这需要你的"task"可以取消。

为了减轻您以前方法的一些问题,您可以捕获视图控制器的 weak 引用而不是 strong 引用定义闭包。这不会阻止异步任务 运行 完成,但在完成处理程序中,您可以检查视图控制器是否仍然存在,如果它不再存在就退出。

并且,如果您需要 "keep" 某个可能在某个任意线程上执行的闭包中的 UIKit 对象,请注意这可能是 last 强引用,并确保最后一个强引用在 main 线程上发布。

另请参阅:Using weak self in dispatch_async function

编辑:

My question would be: do closures have anything special that makes them a good choice in scenarios similar to this one, rather than going back to delegate pattern?

我会说,闭包是许多用例中的 "better" 方法:

委托比闭包更容易出现循环引用等问题(因为它们 "owned" 是一个对象,并且该对象可能被捕获为委托中的变量)。

作为完成处理程序的闭包的经典用例还改进了代码的 "locality",使其更易于理解:您声明 什么 将在任务完成时发生在调用任务的语句之后 - 无论这可能需要多长时间。

与常规 "functions" 相比,闭包的巨大优势在于闭包在定义时捕获整个 "context"。也就是说,它可以在定义时将变量和 "import" 引用到闭包中 - 并在它执行时使用它,无论 什么时候 发生这种情况,并且当定义时的原始 "stack" 已经消失时。