如何使用 Swift 配置后台应用刷新?

Posted

技术标签:

【中文标题】如何使用 Swift 配置后台应用刷新?【英文标题】:How to configure Background App Refresh using Swift? 【发布时间】:2017-10-31 21:48:09 【问题描述】:

我有以下功能可以在我的 SeachVC (UIViewController) 中下载 JSON 数据,效果很好。

func downloadJSON()

    guard let url = URL(string: "myURL") else  return 

    URLSession.shared.dataTask(with: url)  (data, response, err) in
        guard let data = data else  return 

        do 
            let downloadedCurrencies = try JSONDecoder().decode([Currency].self, from: data)

            // Adding downloaded data into Local Array
            Currencies = downloadedCurrencies

         catch let jsonErr 
            print("Here! Error serializing json", jsonErr)
        

        .resume()

为了实现后台应用刷新,我在 App Delegate 中添加了以下功能;

var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool 
    // Override point for customization after application launch.

    // Background App Refresh Config
    UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
    return true


func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) 
    if let VC = window?.rootViewController as? SearchVC 
        // Update JSON data
        VC.downloadJSON()
        completionHandler(.newData)
    

但是,当我在模拟器上模拟后台应用刷新时,我收到警告:

警告:应用程序委托收到了对 -application:performFetchWithCompletionHandler: 的调用,但从未调用过完成处理程序。

我将在哪里实现完成处理程序以及如何实现?

谢谢

【问题讨论】:

【参考方案1】:

如果需要,您需要将下载代码从视图控制器移动到另一个类,或者至少修改您当前的后台刷新方法以实例化视图控制器。当您的应用尚未在前台启动时,可以触发后台刷新,因此if let 将失效。

考虑问题中的代码:

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) 
    if let VC = window?.rootViewController as? SearchVC 
        // Update JSON data
        VC.downloadJSON()
        completionHandler(.newData)
    

如果 if let... 没有通过,那么您退出函数而不调用 completionHandler,因此您会收到 runtime 警告,指出未调用完成处理程序。

您可以修改您的代码以在 else 案例中包含对 completionHandler 的调用,但在这种情况下不会进行任何提取:

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) 
    if let VC = window?.rootViewController as? SearchVC 
        // Update JSON data
        VC.downloadJSON()
        completionHandler(.newData)
     else 
        completionHandler(.noData)
 

或者,如果需要,您可以实例化视图控制器(或者我会建议另一个数据获取类):

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) 
    let vc = (window?.rootViewController as? SearchVC) ?? SearchVC()
        // Update JSON data
    vc.downloadJSON()
    completionHandler(.newData)

您还应该修改您的 downloadJSON 函数以包含一个完成处理程序参数,当 JSON 下载完成时您将调用该参数。这将让您在实际下载数据后调用后台获取完成处理程序:

func downloadJSON(completion: ((Bool,Error?) -> Void )? = nil)) 

    guard let url = URL(string: "myURL") else  
        completion?(false, nil)
        return 
    

    URLSession.shared.dataTask(with: url)  (data, response, err) in

        guard nil == err else 
            completion?(false, err)
            return
        

        guard let data = data else  
            completion?(false, nil)
            return 
        

        do 
            let downloadedCurrencies = try JSONDecoder().decode([Currency].self, from: data)

            // Adding downloaded data into Local Array
            Currencies = downloadedCurrencies
            completion(true,nil)
         catch let jsonErr 
            print("Here! Error serializing json", jsonErr)
            completion?(false,jsonErr)
        

        .resume()



func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) 
    let vc = (window?.rootViewController as? SearchVC) ?? SearchVC()
        // Update JSON data
    vc.downloadJSON()  (newData,error) in
        if let err = error 
           NSLog("Background fetch error: \(err.localizedDescription)")
           completionHandler(.fail)
         else 
            completionHandler(newData ? .newData:.noData)
        
    

2019 年 9 月更新

请注意,ios 13 引入了新的后台获取和处理功能。更多详情请参考WWDC session

【讨论】:

感谢您的出色回答。我是新手,所以请原谅我的问题。如何将 completionHandler 添加到我的 downloadJSON 中?哦,你也加了。非常感谢!将实施您的输入!真的很感激! 最后的部分代码给出了一个错误:参数传递给调用,该调用在 vc.downloadJSON() (error) in 的“”上不接受任何参数 那是因为您需要在下载函数中添加完成处理程序参数 - 请参阅我编辑的答案 完美运行。没有错误。我希望我也可以将您的问题标记为答案。非常感谢您的宝贵时间! completionHandler是系统调用委托方法时提供的。完成后,您有责任使用 fetch 的结果调用它。 .newData 表示获取了新数据。 iOS 使用该结果来确定您的应用应该多久以及何时有机会执行后台提取。【参考方案2】:

这可能是因为您没有在 else 情况下调用 completionHandler(这永远不会发生,但编译器不知道)

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) 
    if let VC = window?.rootViewController as? SearchVC 
        // Update JSON data
        VC.downloadJSON()
        completionHandler(.newData)
     else 
        completionHandler(.failed)
    

【讨论】:

警告消失了。谢谢! 其实这是iOS发出的运行时警告,而不是编译器,因为确实发生了; if 测试失败,因为视图控制器在后台不可用。在 else 中添加对完成处理程序的调用将导致警告消失,因为完成处理程序被调用,但没有发生下载。

以上是关于如何使用 Swift 配置后台应用刷新?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Swift 3 使用后台任务?

即使应用程序在后台运行,如何使用 swift 在 ios 中获取位置更新

在swift中,当应用程序进入后台时,如何使用依赖注入保存变量?

如何使用 Swift 在 Apple Watch 上添加下拉刷新功能

应用程序进入后台时如何保持 Swift Socket IO 运行?

Swift 3:如何在后台扫描外围设备?