你已經做了許多必要的步驟:
話雖如此,一些觀察:
我會檢查應用的權限在「設置」»‘常規’»‘後臺應用刷新’。這可以確保您不僅可以成功請求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,您無法控制後臺提取何時發生。它考慮了許多文檔記載不佳的因素(無線網絡連接,電源連接,用戶應用程序使用頻率,其他應用程序可能旋轉等)。
剛纔我說過,當我啓用後臺抓取時,從設備運行應用程序(而不是Xcode),並將其連接到wifi和電源,第一次後臺抓取在我的iPhone 7+上出現在10分鐘內暫停應用程序。
您的代碼當前沒有執行任何獲取請求。這就提出了兩個問題:在某些時候
確保測試程序實際上發出URLSession
要求其正常的行動當然,當你運行它時(即當您正常運行的應用程序,而不是通過背景提取)。如果您有一個測試應用程序不會發出任何請求,它似乎不會啓用後臺獲取功能。 (或者至少,它嚴重影響後臺獲取請求的頻率)。
據報道,如果之前的後臺獲取調用實際上並未導致網絡請求正在發佈。 (這可能是前一點的變化,但並不完全清楚。)我懷疑蘋果正在試圖阻止開發人員使用後臺獲取機制來處理並不真正獲取任何內容的任務。
注意,您的應用程序並沒有太多的時間來執行請求,所以如果你發出一個請求,你可能要單獨詢問是否有可用的數據,但不嘗試下載所有數據本身。然後,您可以啓動後臺會話以開始耗時的下載。顯然,如果檢索的數據量可以忽略不計,那麼這不太可能成爲問題,但要確保您完成請求後可以合理快速地調用後臺完成(30秒,IIRC)。如果您不在該時間範圍內調用它,則會影響嘗試後續後臺提取請求的情況。
如果應用程序沒有處理後臺請求,我可能會建議從應用程序中刪除應用程序並重新安裝。在測試後臺獲取請求停止工作的情況下(可能是因爲在測試應用的前一次迭代時失敗的後臺獲取請求的結果),我遇到過這種情況。我發現刪除並重新安裝它是重置後臺獲取過程的好方法。
爲了說明起見,這裏是執行背景成功獲取的示例。我還添加了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
}
}
和視圖控制器僅請求許可用戶通知併發出一些隨機請求:
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()
}
}
是的,啓用後臺提取;設置也完成了,權限授予,該應用程序在後臺 – user6539552