時々アプリを起動できるように、バックグラウンドフェッチを実装しようとしました。
私はこれらをしました:
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")
}
バックグラウンドフェッチ機能も既に有効にしています。それは私がやったすべてです。そして、アプリを実行します。この関数は1時間経っても呼び出されません(電話は眠りました)。
関数を呼び出すには、他に何をしなければなりませんか?
必要な手順の多くを完了しました。
プロジェクトの[機能]タブの[バックグラウンドフェッチ]をオンにしました。
setMinimumBackgroundFetchInterval(_:)
in application(_:didFinishLaunchingWithOptions:)
と呼ばれます。
そうは言っても、いくつかの観察:
[設定]"[全般]"[バックグラウンドアプリの更新]でアプリの権限を確認します。これにより、plistでバックグラウンドフェッチを正常にリクエストしただけでなく、一般的に、特にアプリに対しても有効になります。
アプリを強制終了しないようにします(ホームボタンをダブルタップし、アプリをスワイプしてアプリを強制終了します)。アプリが強制終了されると、バックグラウンドフェッチが正しく機能しなくなります。
debugPrint
を使用していますが、Xcodeから実行する場合にのみ機能します。ただし、Xcodeから実行するのではなく、物理デバイスでこれを行う必要があります。 Xcodeを介してアプリを実行していない場合でも、アクティビティを表示するロギングシステムを採用する必要があります。
私が使う os_log
そしてコンソールから見る(WWDC 2016 nified Logging and Activity Tracing を参照)または serNotificationsフレームワーク (WWDC 2016 Introductionを参照Notifications )のように、アプリがバックグラウンドで注目すべきことを行うと通知されます。または、独自の外部ロギングシステムを作成しました(テキストファイルまたはplistへの書き込みなど)。ただし、Xcodeから独立して実行せずにこれをテストするため、print
/debugPrint
の外部でアクティビティを観察する方法が必要です。バックグラウンド関連の動作は、デバッガーに接続されたアプリの実行中に変化します。
PGDevが言った のように、バックグラウンドフェッチがいつ行われるかを制御することはできません。文書化されていない多くの要因(wifi接続、電源に接続されている、ユーザーのアプリの使用頻度、他のアプリが起動している可能性がある場合など)を考慮します。
つまり、バックグラウンドフェッチを有効にし、デバイス(Xcodeではない)からアプリを実行し、それをwifiと電源に接続すると、アプリを中断してから10分以内にiPhone 7+に最初のバックグラウンドフェッチが表示されました。
現在、コードはフェッチリクエストを行っていません。これにより、2つの懸念が生じます。
テストアプリは、実行時にURLSession
リクエストを実際に実行することを確認してください(バックグラウンドフェッチではなく、アプリを通常実行するとき)。リクエストを発行しないテストアプリがある場合、バックグラウンドフェッチ機能を有効にするようには見えません。 (または、少なくとも、バックグラウンドフェッチリクエストの頻度に深刻な影響を及ぼします。)
報告によると、OSは、以前のバックグラウンドフェッチ呼び出しが実際にネットワークリクエストを発行しなかった場合、アプリへの後続のバックグラウンドフェッチ呼び出しの発行を停止します。 (これは前の点の順列かもしれません。完全に明確ではありません。)私は疑いAppleは実際に何もフェッチしていないタスクに対してバックグラウンドフェッチメカニズムを使用する開発者を阻止しようとしています。
アプリにはリクエストを実行する時間があまりないので、リクエストを発行している場合は、利用可能なデータがあるかどうかだけを問い合わせたいが、すべてのデータ自体をダウンロードしようとしない場合があります。その後、バックグラウンドセッションを開始して、時間のかかるダウンロードを開始できます。明らかに、取得されるデータの量が無視できる場合、これは懸念されることはほとんどありませんが、バックグラウンド完了を適切に迅速に呼び出してリクエストを完了してください(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
}
}
そして、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()
}
}
Background Fetchは、適切な間隔でシステムによって自動的に開始されます。
Background Fetchの非常に重要でクールな機能は、アプリをバックグラウンドで起動して更新できる時間を学習できることです。たとえば、ユーザーが毎朝午前8時30分頃にニュースアプリを使用するとします(ホットコーヒーと一緒にニュースを読む)。数回使用した後、システムは、次回アプリが実行されるのがほぼ同時になる可能性が高いことを学習します。そのため、通常の起動時間の前にアプリを公開し、更新するように注意します(午前8時頃)。そうすれば、ユーザーがアプリを開くと、新しく更新されたコンテンツが彼を待っており、反対ではありません!この機能は、使用量予測と呼ばれます。
書いたコードが適切に動作するかどうかをテストするには、RaywenderlichのチュートリアルonBackground Fetch。
チュートリアル: https://www.raywenderlich.com/143128/background-modes-tutorial-getting-started(Search for:Testing Fetch Background Fetch)