草庐IT

iOS10 后台获取

coder 2023-07-16 原文

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

我做了这些:

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

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

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

最佳答案

您已经完成了许多必要的步骤:

  • 打开项目的“功能”选项卡中的“后台获取”;
  • 已实现 application(_:performFetchWithCompletionHandler:) ;
  • 调用 setMinimumBackgroundFetchInterval(_:) application(_:didFinishLaunchingWithOptions:) .

  • 话虽如此,有几点意见:
  • 我会在“设置”»“常规”»“后台应用程序刷新”中检查应用程序的权限。这可以确保您不仅在 plist 中成功请求后台提取,而且它在一般情况下是启用的,特别是为您的应用程序启用。
  • 确保您没有终止应用程序(即通过双击主页按钮并在您的应用程序上向上滑动以强制应用程序终止)。如果应用程序被终止,它将阻止后台提取正常工作。
  • 您正在使用 debugPrint ,但这仅在从 Xcode 运行时才有效。但是您应该在物理设备上执行此操作,而不是从 Xcode 运行它。即使不通过 Xcode 运行应用程序,您也需要使用一个日志系统来显示您的事件。

    我用 os_log并从控制台观看(参见 WWDC 2016 Unified Logging and Activity Tracing)或使用通过 UserNotifications framework 发布通知(参见 WWDC 2016 Introduction to Notifications )所以当应用程序在后台执行一些值得注意的事情时我会收到通知。或者我已经创建了自己的外部日志系统(例如写入一些文本文件或 plist)。但是您需要某种方式来观察 print 之外的事件/debugPrint因为你想在不独立于 Xcode 运行它的同时测试它。在运行连接到调试器的应用程序时,任何与后台相关的行为都会发生变化。
  • PGDev said ,您无法控制后台获取的时间。它考虑了许多记录不足的因素(wifi 连接性、连接到电源、用户的应用程序使用频率、其他应用程序可能何时启动等)。

    话虽如此,当我启用后台提取,从设备(不是 Xcode)运行应用程序,并将其连接到 wifi 和电源时,在暂停应用程序的 10 分钟内,第一个调用的后台提取出现在我的 iPhone 7+ 上。
  • 您的代码当前未执行任何提取请求。这引起了两个担忧:
  • 确保测试应用实际发出 URLSession当您运行它时(即当您正常运行应用程序时,而不是通过后台获取时),在某个时候请求它的正常操作过程。如果您有一个不发出任何请求的测试应用程序,它似乎没有启用后台提取功能。 (或者至少,它严重影响了后台获取请求的频率。)
  • 据报道,如果先前的后台提取调用实际上并未导致发出网络请求,操作系统将停止向您的应用发出后续的后台提取调用。 (这可能是前一点的排列;它并不完全清楚。)我怀疑 Apple 正试图阻止开发人员将后台获取机制用于未真正获取任何内容的任务。
  • 请注意,您的应用程序没有太多时间来执行请求,因此如果您正在发出请求,您可能只想查询是否有可用数据,而不是尝试下载所有数据本身。然后,您可以启动后台 session 以开始耗时的下载。显然,如果检索的数据量可以忽略不计,那么这不太可能成为问题,但请确保您以合理的速度完成请求,调用后台完成(30 秒,IIRC)。如果您不在该时间范围内调用它,它将影响是否/何时尝试后续后台提取请求。
  • 如果应用程序不处理后台请求,我可能建议从设备中删除应用程序并重新安装。我遇到过这样的情况,在测试后台获取时请求停止工作(可能是由于测试应用程序的前一次迭代时后台获取请求失败)。我发现删除并重新安装它是重置后台获取过程的好方法。


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

    View Controller 仅请求用户通知的许可并发出一些随机请求:
    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 后台获取,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44313278/

    有关iOS10 后台获取的更多相关文章

    1. ruby - 简单获取法拉第超时 - 2

      有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url

    2. ruby - 如何验证 IO.copy_stream 是否成功 - 2

      这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

    3. ruby - 从 Ruby 中的主机名获取 IP 地址 - 2

      我有一个存储主机名的Ruby数组server_names。如果我打印出来,它看起来像这样:["hostname.abc.com","hostname2.abc.com","hostname3.abc.com"]相当标准。我想要做的是获取这些服务器的IP(可能将它们存储在另一个变量中)。看起来IPSocket类可以做到这一点,但我不确定如何使用IPSocket类遍历它。如果它只是尝试像这样打印出IP:server_names.eachdo|name|IPSocket::getaddress(name)pnameend它提示我没有提供服务器名称。这是语法问题还是我没有正确使用类?输出:ge

    4. ruby - 获取模块中定义的所有常量的值 - 2

      我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c

    5. ruby-on-rails - 获取 inf-ruby 以使用 ruby​​ 版本管理器 (rvm) - 2

      我安装了ruby​​版本管理器,并将RVM安装的ruby​​实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby​​。有没有办法让emacs像shell一样尊重ruby​​的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el

    6. Ruby 文件 IO 定界符? - 2

      我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

    7. Ruby 从大范围中获取第 n 个项目 - 2

      假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit

    8. ruby - Net::HTTP 获取源代码和状态 - 2

      我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

    9. ruby - 没有类方法获取 Ruby 类名 - 2

      如何在Ruby中获取BasicObject实例的类名?例如,假设我有这个:classMyObjectSystem我怎样才能使这段代码成功?编辑:我发现Object的实例方法class被定义为returnrb_class_real(CLASS_OF(obj));。有什么方法可以从Ruby中使用它? 最佳答案 我花了一些时间研究irb并想出了这个:classBasicObjectdefclassklass=class这将为任何从BasicObject继承的对象提供一个#class您可以调用的方法。编辑评论中要求的进一步解释:假设你有对象

    10. ruby-on-rails - 如何在 Gem 中获取 Rails 应用程序的根目录 - 2

      是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在

    随机推荐