2017-06-01 54 views
3

我試圖實現後臺抓取,希望能夠不時地喚醒應用程序。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") 
    } 

我也啓用後臺已經獲取能力。這就是我所做的一切。然後我運行該應用程序。該功能即使在一小時後也不會被調用(電話睡眠)。

我還需要做些什麼來使該函數被調用?

+0

是的,啓用後臺提取;設置也完成了,權限授予,該應用程序在後臺 – user6539552

回答

0

後臺提取由系統以適當的時間間隔自動啓動。

背景的非常重要的,酷的功能是獲取其 學習應該允許一個應用程序被啓動,以 背景並獲得更新的時間的能力。假設例如用戶 每天早上大約早上8:30使用新聞應用程序(閱讀一些消息以及 一些熱咖啡)。經過幾次使用後,系統得知 下一次應用運行的可能性大約是 ,因此在啓動時間前需要注意讓它啓動並更新 (它可能在上午8點左右)。這樣, 當用戶打開應用程序時,新的和刷新的內容有 等待他,而不是相反!此功能稱爲用法 預測

爲了測試是否你寫的作品得當與否的代碼,你可以參考Raywenderlich的教程背景提取

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

4

你已經做了許多必要的步驟:

話雖如此,一些觀察:

  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,您無法控制後臺提取何時發生。它考慮了許多文檔記載不佳的因素(無線網絡連接,電源連接,用戶應用程序使用頻率,其他應用程序可能旋轉等)。

    剛纔我說過,當我啓用後臺抓取時,從設備運行應用程序(而不是Xcode),並將其連接到wifi和電源,第一次後臺抓取在我的iPhone 7+上出現在10分鐘內暫停應用程序。

  5. 您的代碼當前沒有執行任何獲取請求。這就提出了兩個問題:在某些時候

    • 確保測試程序實際上發出URLSession要求其正常的行動當然,當你運行它時(即當您正常運行的應用程序,而不是通過背景提取)。如果您有一個測試應用程序不會發出任何請求,它似乎不會啓用後臺獲取功能。 (或者至少,它嚴重影響後臺獲取請求的頻率)。

    • 據報道,如果之前的後臺獲取調用實際上並未導致網絡請求正在發佈。 (這可能是前一點的變化,但並不完全清楚。)我懷疑蘋果正在試圖阻止開發人員使用後臺獲取機制來處理並不真正獲取任何內容的任務。

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

}