【问题标题】:How to configure Background App Refresh using Swift?如何使用 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: 的调用,但从未调用过完成处理程序。

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

谢谢

【问题讨论】:

    标签: ios swift background-process appdelegate completionhandler


    【解决方案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 中添加对完成处理程序的调用将导致警告消失,因为完成处理程序被调用,但没有发生下载。
    猜你喜欢
    • 2016-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-29
    • 1970-01-01
    • 1970-01-01
    • 2016-04-24
    相关资源
    最近更新 更多