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

How to configure Background App Refresh using Swift?

我有以下功能可以在我的 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: 的调用,但从未调用完成处理程序。

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

谢谢

这可能是因为您没有在 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)
    }
}

您需要将下载代码从视图控制器移动到另一个 class 或至少修改您当前的后台刷新方法以在需要时实例化视图控制器。当您的应用程序尚未在前台启动时,可以触发后台刷新,因此 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)
} 

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

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 以获取更多详细信息