iOS10后台提取

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

我做了这些:

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") } 

我已经启用了后台提取function。 这就是我所做的一切。 然后我运行应用程序。 即使一小时后电话也没有打电话(手机睡了)。

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

您已完成许多必要步骤:

  • 打开“后台获取”项目的“function”选项卡;

  • 实现了application(_:performFetchWithCompletionHandler:) ;

  • application(_:didFinishLaunchingWithOptions:)调用setMinimumBackgroundFetchInterval(_:) ) application(_:didFinishLaunchingWithOptions:)

有人说过,有几点意见:

  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请求。 如果您的测试应用程序没有发出任何请求,则它似乎不会启用后台提取function。 (或者至少,它会严重影响后台获取请求的频率。)

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

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

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


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

 // 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 (ie 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 } } 

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

 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() } } 

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

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

为了测试您编写的代码是否正常工作,您可以参考Raywenderlich的 Background Fetch 教程

教程 : https : //www.raywenderlich.com/143128/background-modes-tutorial-getting-started (搜索:测试后台获取)