在 Alamofire 中使用 backgroundCompletionHandler 的正确方法是什么?

What is the correct way to use backgroundCompletionHandler in Alamofire?

我不清楚如何正确使用它,但看到其他人在做这种事情:

func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) {
    manager.sharedInstance.backgroundCompletionHandler = completionHandler
}

在我们类似的实现中,此时completionHandlerpartial apply forwarder for reabstraction thunk helper...

其中 manager 本质上是(尽管是单身人士):

let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("com.ourcompany.app")
let manager = Alamofire.Manager(configuration: configuration)

然而,这会导致在控制台中打印以下警告:

Warning: Application delegate received call to -application:handleEventsForBackgroundURLSession:completionHandler: but the completion handler was never called.

我设置了一个断点 here 并且此时消息已经在控制台中可见并且 backgroundCompletionHandlernil.

我们正在使用 Xcode 7.0 针对 iOS 9 SDK 进行构建,目前使用的是 Alamofire 2.0.2

我最初认为这是在我们合并 Swift 2.0 分支时引入的,但我也看到了使用 Xcode 6.4 针对 iOS 8 SDK 进行的较早提交的消息.


更新 1

解决@cnoon 的建议:

我应该注意到,在尝试从我的 phone 上传一些屏幕截图时,我最初无法重现此问题以尝试这些建议。

只有在尝试分享一些照片后,我才能再次重现这一点。我不确定或相关性(如果有的话),但这可能与上传时间更长的照片有关。


更新 2

UIBackgroundModes 的设置与@Nick 描述的完全一致,直接在 application:handleEventsForBackgroundURLSession:completionHandler: 内部调用 completionHandler() 不会显示警告。


更新 3

看来我忽略了一个小而重要的细节。 Alamofire.Manager 周围有一个包装器,不会直接公开它。其实现的相关部分如下所示:

private var manager: Manager

public var backgroundCompletionHandler: (() -> Void)? {
    get {
        return manager.backgroundCompletionHandler
    }
    set {
        manager.backgroundCompletionHandler = backgroundCompletionHandler
    }
}

并在 AppDelegate 中设置完成处理程序以执行该代码路径。

func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) {
    myManager.sharedInstance.backgroundCompletionHandler = completionHandler
}

我已确认以下公开 Alamofire.Manager 实例并直接访问它的更改不会产生警告:

public var manager: Manager

//    public var backgroundCompletionHandler: (() -> Void)? {
//        get {
//            return manager.backgroundCompletionHandler
//        }
//        set {
//            manager.backgroundCompletionHandler = backgroundCompletionHandler
//        }
//    }

并在 AppDelegate 中:

func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) {
    myManager.sharedInstance.manager.backgroundCompletionHandler = completionHandler
}

据此看来,使用计算的 属性 来代理完成处理程序似乎是问题的原因。

我真的不想公开这个 属性 并且很想知道任何解决方法或替代方案。

看来你所做的一切都是正确的。我有一个示例应用程序,它完全按照您所描述的方式工作,并且不会引发您看到的警告。我猜你在某处仍然有一些小错误。以下是一些可以尝试的想法:

  • 验证标识符与后台会话的标识符匹配
  • Manager class 中的 backgroundSessionHandler 上临时添加 didSet 日志语句以验证它是否已设置
  • 将日志语句添加到 sessionDidFinishEventsForBackgroundURLSession 以验证它是否按预期被调用
  • 覆盖委托上的 sessionDidFinishEventsForBackgroundURLSession 并手动调用 backgroundSessionHandler
  • 确认您的 Xcode 项目已为后台会话正确设置

更新 2

您计算的 属性 是错误的。相反,它需要将 backgroundCompletionHandler 设置为 newValue。否则你永远不会正确地将它设置为新值。请参阅此 thread 了解更多信息。