iOS10后台获取

use*_*552 12 ios swift background-fetch

我试图实现后台提取,希望可以不时唤醒应用程序.

我做了这些:

      func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
        return true
      }

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    debugPrint("performFetchWithCompletionHandler")
    getData()
    completionHandler(UIBackgroundFetchResult.newData)
  }

  func getData(){
    debugPrint("getData")
  }
Run Code Online (Sandbox Code Playgroud)

我已经启用了后台提取功能.这就是我所做的一切.然后我运行应用程序.即使一小时后(手机睡了),这个功能也从未打过电话.

还有什么其他的事情可以让函数被调用?

Rob*_*Rob 14

您已完成许多必要步骤:

话虽如此,有几点意见:

  1. 我将在"设置"»"常规"»"后台应用刷新"中检查应用的权限.这样可以确保您不仅成功地在plist中请求后台提取,而且通常也会启用它,特别是对于您的应用程序.

  2. 确保你没有杀死应用程序(即双击主页按钮并向上滑动你的应用程序以强制终止应用程序).如果应用程序被终止,则会阻止后台获取正常工作.

  3. 您正在使用debugPrint,但只有在从Xcode运行时才有效.但是你应该在物理设备上执行此操作,而不是从Xcode运行它.即使不通过Xcode运行应用程序,您也需要使用一个日志系统来显示您的活动.

    os_log在控制台中使用和观看它(参见WWDC 2016 统一记录和活动跟踪)或使用UserNotifications框架发布通知(请参阅WWDC 2016 通知简介),以便在应用程序在后台执行某些操作时通知我.或者我已经创建了自己的外部日志系统(例如写入一些文本文件或plist).但是你需要一些方法来观察print/ 之外的活动,debugPrint因为你想要测试它而不是独立于Xcode运行它.运行连接到调试器的应用程序时,任何与背景相关的行为都会发生变化.

  4. 正如PGD​​ev所说,你无法控制何时进行后台提取.它考虑了许多记录不佳的因素(WiFi连接,连接到电源,用户的应用程序使用频率,其他应用程序何时可能正在启动等).

    话虽如此,当我启用后台提取时,从设备(不是Xcode)运行应用程序,并将其连接到wifi和电源,在暂停应用程序的10分钟内,我的iPhone 7+上出现了第一个背景提取.

  5. 您的代码当前没有执行任何获取请求.这引起了两个问题:

    • 确保测试应用程序URLSession在运行时实际发出请求(即正常运行应用程序时,而不是通过后台获取).如果您的测试应用程序没有发出任何请求,则它似乎不会启用后台提取功能.(或者至少,它严重影响后台获取请求的频率.)

    • 据报道,如果先前的后台提取呼叫实际上没有导致发出网络请求,操作系统将停止向您的应用发出后续后台提取呼叫.(这可能是前一点的排列;它并不完全清楚.)我怀疑Apple正试图阻止开发人员使用后台获取机制来处理那些并非真正取得任何东西的任务.

  6. 请注意,您的应用没有太多时间来执行请求,因此,如果您要发出请求,您可能只想询问是否有可用数据,而不是尝试自行下载所有数据.然后,您可以启动后台会话以启动耗时的下载.显然,如果检索的数据量可以忽略不计,那么这不太可能是一个问题,但请确保完成您的请求,合理地快速调用后台完成(30秒,IIRC).如果您未在该时间范围内调用它,则会影响是否/何时尝试后续后台提取请求.

  7. 如果应用程序未处理后台请求,我可能会建议您从设备中删除该应用并重新安装.在测试请求停止工作的后台提取时,我遇到了这种情况(可能是因为测试应用程序的上一次迭代时背景提取请求失败).我发现删除并重新安装它是重置后台获取过程的好方法.


为了便于说明,这里是一个成功执行后台提取的示例.我还添加了UserNotifications框架和os_log调用,以提供一种在未连接到Xcode时监视进度的方法(即,在哪里print,debugPrint不再有用):

// AppDelegate.swift

import UIKit
import UserNotifications
import os.log

@UIApplicationMain
class AppDelegate: UIResponder {

    var window: UIWindow?

    /// The URLRequest for seeing if there is data to fetch.

    fileprivate var fetchRequest: URLRequest {
        // create this however appropriate for your app
        var request: URLRequest = ...
        return request
    }

    /// A `OSLog` with my subsystem, so I can focus on my log statements and not those triggered 
    /// by iOS internal subsystems. This isn't necessary (you can omit the `log` parameter to `os_log`,
    /// but it just becomes harder to filter Console for only those log statements this app issued).

    fileprivate let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "log")

}

// MARK: - UIApplicationDelegate

extension AppDelegate: UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        // turn on background fetch

        application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)

        // issue log statement that app launched

        os_log("didFinishLaunching", log: log)

        // turn on user notifications if you want them

        UNUserNotificationCenter.current().delegate = self

        return true
    }

    func applicationWillEnterForeground(_ application: UIApplication) {
        os_log("applicationWillEnterForeground", log: log)
    }

    func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        os_log("performFetchWithCompletionHandler", log: log)
        processRequest(completionHandler: completionHandler)
    }

}

// MARK: - UNUserNotificationCenterDelegate

extension AppDelegate: UNUserNotificationCenterDelegate {

    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        os_log("willPresent %{public}@", log: log, notification)
        completionHandler(.alert)
    }

    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        os_log("didReceive %{public}@", log: log, response)
        completionHandler()
    }
}

// MARK: - Various utility methods

extension AppDelegate {

    /// Issue and process request to see if data is available
    ///
    /// - Parameters:
    ///   - prefix: Some string prefix so I know where request came from (i.e. from ViewController or from background fetch; we'll use this solely for logging purposes.
    ///   - completionHandler: If background fetch, this is the handler passed to us by`performFetchWithCompletionHandler`.

    func processRequest(completionHandler: ((UIBackgroundFetchResult) -> Void)? = nil) {
        let task = URLSession.shared.dataTask(with: fetchRequest) { data, response, error in

            // since I have so many paths execution, I'll `defer` this so it captures all of them

            var result = UIBackgroundFetchResult.failed
            var message = "Unknown"

            defer {
                self.postNotification(message)
                completionHandler?(result)
            }

            // handle network errors

            guard let data = data, error == nil else {
                message = "Network error: \(error?.localizedDescription ?? "Unknown error")"
                return
            }

            // my web service returns JSON with key of `success` if there's data to fetch, so check for that

            guard
                let json = try? JSONSerialization.jsonObject(with: data),
                let dictionary = json as? [String: Any],
                let success = dictionary["success"] as? Bool else {
                    message = "JSON parsing failed"
                    return
            }

            // report back whether there is data to fetch or not

            if success {
                result = .newData
                message = "New Data"
            } else {
                result = .noData
                message = "No Data"
            }
        }
        task.resume()
    }

    /// Post notification if app is running in the background.
    ///
    /// - Parameters:
    ///
    ///   - message:           `String` message to be posted.

    func postNotification(_ message: String) {

        // if background fetch, let the user know that there's data for them

        let content = UNMutableNotificationContent()
        content.title = "MyApp"
        content.body = message
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
        let notification = UNNotificationRequest(identifier: "timer", content: content, trigger: trigger)
        UNUserNotificationCenter.current().add(notification)

        // for debugging purposes, log message to console

        os_log("%{public}@", log: self.log, message)  // need `public` for strings in order to see them in console ... don't log anything private here like user authentication details or the like
    }

}
Run Code Online (Sandbox Code Playgroud)

并且视图控制器仅请求用户通知的许可并发出一些随机请求:

import UIKit
import UserNotifications

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // request authorization to perform user notifications

        UNUserNotificationCenter.current().requestAuthorization(options: [.sound, .alert]) { granted, error in
            if !granted {
                DispatchQueue.main.async {
                    let alert = UIAlertController(title: nil, message: "Need notification", preferredStyle: .alert)
                    alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                    self.present(alert, animated: true, completion: nil)
                }
            }
        }

        // you actually have to do some request at some point for background fetch to be turned on;
        // you'd do something meaningful here, but I'm just going to do some random request...

        let url = URL(string: "http://example.com")!
        let request = URLRequest(url: url)
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            DispatchQueue.main.async {
                let alert = UIAlertController(title: nil, message: error?.localizedDescription ?? "Sample request finished", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                self.present(alert, animated: true)
            }
        }
        task.resume()
    }

}
Run Code Online (Sandbox Code Playgroud)


PGD*_*Dev 5

背景提取由系统以适当的间隔自动启动.

Background Fetch的一个非常重要和很酷的功能是它能够学习应该允许应用程序启动到后台并获得更新的时间.让我们假设用户每天早上8:30左右使用新闻应用程序(阅读一些新闻以及一些热咖啡).经过几次使用后,系统得知下次应用程序运行时很可能会在同一时间运行,因此需要注意让它上线并在通常的启动时间之前得到更新(可能是早上8点左右).这样,当用户打开应用程序时,新的和刷新的内容在等待他,而不是相反!此功能称为使用预测.

为了测试是否你写的作品得当与否的代码,你可以参考Raywenderlich的教程后台获取.

教程:https ://www.raywenderlich.com/143128/background-modes-tutorial-getting-started (搜索:测试背景提取)