我正在处理网络请求类,我担心崩溃。例如,在将回调方法传递给函数时,使用闭包非常简单:
// 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
类)交互时,它可能容易在像
因此,当回调最终被触发时,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指针而不会导致崩溃)将会很好。感谢。
答案 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
关于应用程序输入后台/前台,您可以通过覆盖applicationDidEnterBackground
和applicationWillEnterForeground
或注册相应的通知来暂停/恢复操作:UIApplicationWillEnterForegroundNotification
/ UIApplicationDidEnterBackgroundNotification
我强烈建议您使用AFNetworking,因为它的API提供了我上面提到的所有内容,等等。