iOS10后台提取

扫码查看
本文介绍了iOS10后台提取的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have tried to implement background fetch, to hopefully can wake the app from time to time.

我做过这些:

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

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

I have also enable background fetch capabilities already. That's all i have done. And then i run the app. the function never called even after an hour (the phone slept).

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

What other things i have to do to make the function get called?

推荐答案

您已完成许多必要步骤:

You have done many of the necessary steps:


  • 打开后台获取项目的功能标签;

  • Turned on "background fetch" the the "Capabilities" tab of your project;

已实施;

名为 noreferrer> application(_:didFinishLaunchingWithOptions :)

有人说过,有几点意见:

That having been said, a couple of observations:


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

  1. I'd check the permissions for the app in "Settings" » "General" » "Background App Refresh". This ensures that not only did you successfully request background fetch in your plist, but that it's enabled in general, as well as for your app in particular.

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

Make sure you're not killing the app (i.e. by double tapping on the home button and swiping up on your app for force the app to terminate). If the app is killed, it will prevent background fetch from working correctly.

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

You're using debugPrint, but that only works when running it from Xcode. But you should be doing this on a physical device, not running it from Xcode. You need to employ a logging system that shows you activity even when not running the app through Xcode.

我使用 os_log 并从控制台观看(参见WWDC 2016 )或通过发布通知(请参阅WWDC 2016 )所以当应用程序在后台做了一些值得注意的事情时,我会收到通知。或者我已经创建了自己的外部日志记录系统(例如写入某些文本文件或plist)。但是你需要某种方法来观察 print / debugPrint 之外的活动,因为你想在不运行它时测试它独立于Xcode。运行连接到调试器的应用程序时,任何与背景相关的行为都会发生变化。

I use os_log and watch it from the Console (see WWDC 2016 Unified Logging and Activity Tracing) or use post a notification via the UserNotifications framework (see WWDC 2016 Introduction to Notifications) so I'm notified when app does something notable in the background. Or I've created my own external logging systems (e.g. writing to some text file or plist). But you need some way of observing the activity outside of print/debugPrint because you want to test this while not running it independently of Xcode. Any background-related behaviors change while running an app connected to the debugger.

As ,你无法控制何时进行后台获取。它考虑了许多记录不佳的因素(wifi连接,连接到电源,用户的应用程序使用频率,其他应用程序何时可能正在启动等)。

As PGDev said, you don't have control over when the background fetch takes place. It considers many poorly documented factors (wifi connectivity, connected to power, user's app usage frequency, when other apps might be spinning up, etc.).

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

That having been said, when I enabled background fetch, ran the app from the device (not Xcode), and had it connected to wifi and power, the first background fetch called appeared on my iPhone 7+ within 10 minutes of suspending the app.

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

Your code isn't currently doing any fetch request. That raises two concerns:


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

  • Make sure that the test app actually issues URLSession request at some point its normal course of action when you run it (i.e. when you run the app normally, not via background fetch). If you have a test app that doesn't issue any requests, it doesn't appear to enable the background fetch feature. (Or at the very least, it severely affects the frequency of the background fetch requests.)

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

Reportedly, the OS will stop issuing subsequent background fetch calls to your app if prior background fetch calls didn't actually result in a network request being issued. (This may be a permutation of the prior point; it's not entirely clear.) I suspect Apple is trying to prevent developers using background fetch mechanism for tasks that aren't really fetching anything.

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

Note, your app doesn't have much time to perform the request, so if you are issuing a request, you might want to inquire solely whether there is data available, but not try to download all the data itself. You can then initiate a background session to start the time consuming downloads. Obviously, if the amount of data being retrieved is negligible, then this is unlikely to be a concern, but make sure you finish your request call the background completion reasonably quickly (30 seconds, IIRC). If you don't call it within that timeframe, it will affect if/when subsequent background fetch requests are attempted.

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

If the app is not processing background requests, I might suggest removing the app from the device and reinstalling. I've had situation where, when testing background fetch where the requests stopped working (possibly as a result of a failed background fetch request when testing a previous iteration of the app). I find that removing and re-installing it is a good way to reset the background fetch process.






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


For sake of illustration, here is an example that performs background fetches successfully. I've also added UserNotifications framework and os_log calls to provide a way of monitoring the progress when not connected to Xcode (i.e. where print and debugPrint no longer are useful):

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

}

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

And the view controller merely requests permission for user notifications and issues some random request:

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

}

这篇关于iOS10后台提取的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-04 14:59
查看更多