我有一个iOS应用程序,在某些情况下会向服务器创建多个请求以填充屏幕。为了加快速度,我们将请求同时运行。为此,我们使用了dispatch_group_enter,dispatch_group_leave和dispatch_group_notify来确保在返回所有答案之前未填充屏幕:
performRequest1() // calls dispatch_group_enter before sending the request and dispatch_group_leave when receiving the response
performRequest2() // calls dispatch_group_enter before sending the request and dispatch_group_leave when receiving the response
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue()) {
populateScreen()
}
当我们收到两个服务器请求的响应之前,用户按下后退按钮时会出现问题。在这种情况下,我们有一个自动取消对服务器(1)的请求的设置,因此不会调用我的成功/失败块(2)。因此,永远不会调用dispatch_group_leave。我认为这样会好(我不希望我的populateScreen()方法被调用)。但iOS似乎有a bug阻止信号量在返回其初始状态之前被释放(3)。换句话说,我被迫调用dispatch_group_enter与dispatch_group_leave完全相同的次数。即使在我只是想跳过并取消所有内容的情况下。
因为对我的申请进行重大改革以取消"取消"发送给视图控制器(4)执行上述代码的操作,我想听听是否有人想要更好的替代方案,等待多个请求完成加载?或者有更好的方法来使用dispatch_group_enter / leave?
修改
一些澄清要点(回应@Rob Napier的答案):
(1)使用AFNetworkings内置的AFHTTPRequestOperation.cancel()函数实现取消
(2)AFNetworking 当然在发送取消操作时调用失败块。但是在我处理的遗留代码中,有一个网络层处理取消操作,因此不会将故障发送到执行请求的类。正如@Rob Napier建议的那样,这很可能是错误的根源,但是在一个大应用程序中改变这种架构设计需要一点勇气......: - 因此,我更愿意找到一个解决方案,我可以在那里取消" abort"取消分配viewController时等待populateScreen()调用。
(3)我知道这篇文章指的是一些不同的东西。然而,我们的应用程序中的崩溃导致Xcode停在与该帖子具有完全相同代码的点:
0x110bab17a <+61>: jne 0x110bab19c ; <+95>
0x110bab17c <+63>: leaq 0x189d0(%rip), %rcx ; "BUG IN CLIENT OF LIBDISPATCH: Use-after-free of dispatch_semaphore_t"
0x110bab183 <+70>: movq %rcx, 0x23316(%rip) ; gCRAnnotations + 8
0x110bab18a <+77>: ud2
0x110bab18c <+79>: leaq 0x18972(%rip), %rcx ; "BUG IN CLIENT OF LIBDISPATCH: Semaphore/group object deallocated while in use"
0x110bab193 <+86>: movq %rcx, 0x23306(%rip) ; gCRAnnotations + 8
-> 0x110bab19a <+93>: ud2
好吧,它可能不是iOS中的错误,只是一个非常讨厌的设计决定; - )
(4)这是一个很大的简化,没有列出我们应用程序的整个架构。更确切地说:我们有一个服务层来执行网络请求并创建返回给视图控制器的模型: - )
答案 0 :(得分:4)
首先,这不是一个错误。您提供的链接正在讨论dispatch_semaphore
,这是相关但不同的。在该线程中,他们注意到即使 也不是bug。文档很清楚:“调用[dispatch_group_enter]必须与调用dispatch_group_leave进行平衡。”这是预期的行为。
在这种情况下,我们有一个自动取消对服务器的请求的设置,因此不会调用我的成功/失败块。
这是错误的。无论你在这里用“取消”来表示什么,都应该产生一个失败条件,导致调用失败块。例如,如果您在cancel
上致电NSURLSessionTask
,代表将收到错误消息。您的performRequest()
需要这样做。你是如何实施“取消?”的
这意味着取消时会调用populateScreen()
。这应该没问题,因为你的系统必须已经能够处理请求错误,而取消只是另一种错误。
作为一个单独的问题,如果上面的代码在视图控制器中,它可能在错误的地方。视图控制器不应该进行网络呼叫并等待结果。他们应该只观察他们的模型。每当模型发生变化时,它们都应该改变。他们应该将用户的请求传递给模型,让模型与网络通信。在这种特殊情况下,这可能只是将问题从视图控制器移动到模型,因此可能不会改变很多事情。但这意味着视图控制器应该能够在取消完成之前解除分配。这很重要,因为视图控制器在弹出后不应该被保留。