闭包与委托模式

时间:2015-06-11 14:33:22

标签: ios swift delegates closures

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

// 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
    }
}

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

这样的情况下崩溃
  1. 当异步任务仍在执行时,用户导航到另一个View Controller
  2. 用户在异步任务仍在执行时按下主页按钮
  3. 等等...
  4. 因此,当回调最终被触发时,self.myLabel.text可能会导致崩溃,因为self所引用的视图控制器可能已经被释放。

    到目前为止。我是对的还是在内部迅速实施某些内容以便永远不会发生这种情况?

    如果我是对的,那么当委托模式派上用场时,这就是委托变量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,如果视图控制器(实现nil协议)被解除分配,它将指向NetworkClientDelegate在这种情况下不会调用回调。

    我的问题是:闭包有什么特别的东西,使它们成为类似于这个场景的好选择,而不是回到委托模式?如果提供闭包的例子(由于nil指针而不会导致崩溃)将会很好。感谢。

2 个答案:

答案 0 :(得分:14)

  

因此,当回调最终被触发时,self.myLabel.text可能会导致崩溃,因为自己所引用的View Controller可能已被释放。

如果已将self作为强引用导入到闭包中,则可保证self

}也就是说,当调用闭包时,视图控制器仍处于活动状态 - 即使此时视图不可见。语句self.myLabel.text = "it succeeded"将被执行,但即使标签不可见,也不会崩溃。

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

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

一种合适的方法需要“取消”一个异步任务,当它被“关闭”时,视图控制器不再需要该结果。当然,这要求您的“任务”可以取消。

为了缓解以前方法的一些问题,您可能会在定义闭包时捕获视图控制器的引用,而不是引用。这不会阻止异步任务运行完成,但是在完成处理程序中,您可以检查视图控制器是否仍然存活,如果它不存在则只是挽救。

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

另请参阅:Using weak self in dispatch_async function

编辑:

  

我的问题是:闭包有什么特别的东西,使它们成为类似于这个场景的好选择,而不是回到委托模式?

我想说,在许多用例中,闭包是“更好”的方法:

代理更容易出现像循环引用而不是闭包这样的问题(因为它们被对象“拥有”,并且该对象可能被捕获为委托中的变量)。

关闭作为完成处理程序的经典用例还改善了代码的“局部性”,使其更易于理解:当任务在之后完成时,将会发生 em>调用任务的语句 - 无论可能需要多长时间。

闭包与常规“函数”的巨大优势在于闭包在定义时捕获整个“上下文”。也就是说,它可以引用变量并在定义它时将它们“导入”闭包 - 并在执行时使用它,无论何时发生,以及当原始的“堆栈”时“在定义时,时间已经过去了。

答案 1 :(得分:0)

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

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

或者,您可以在更新UI之前验证视图控制器的视图是否可见:

viewController.isViewLoaded && viewController.view.window

关于应用程序输入后台/前台,您可以通过覆盖applicationDidEnterBackgroundapplicationWillEnterForeground或注册相应的通知来暂停/恢复操作:UIApplicationWillEnterForegroundNotification / UIApplicationDidEnterBackgroundNotification

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