iOS10后台获取

时间:2017-06-01 16:59:56

标签: 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")
  }

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

我还需要做些什么才能调用该函数?

2 个答案:

答案 0 :(得分:11)

您已完成许多必要步骤:

有人说过,有几点意见:

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

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

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

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

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

    有人说,当我启用后台提取时,从设备(不是Xcode)运行应用程序,并将其连接到wifi和电源,第一个后台提取调用在我的iPhone 7+上显示在暂停后的10分钟内该应用程序。

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

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

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

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

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

  8. 为了便于说明,这里是一个成功执行后台提取的示例。我还添加了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 (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
        }
    
    }
    

    视图控制器仅请求用户通知权限并发出一些随机请求:

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

答案 1 :(得分:6)

系统会以适当的时间间隔自动启动

后台提取

  

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

为了测试您编写的代码是否正常,您可以参考背景提取上的 Raywenderlich教程

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