web-dev-qa-db-ja.com

Swiftを使用してバックグラウンドアプリの更新を構成するにはどうすればよいですか?

SeachVC(UIViewController)にJSONデータをダウンロードするための次の関数があります。これは完璧に機能します。

func downloadJSON(){

    guard let url = URL(string: "myURL") else { return }

    URLSession.shared.dataTask(with: url) { (data, response, err) in
        guard let data = data else { return }

        do {
            let downloadedCurrencies = try JSONDecoder().decode([Currency].self, from: data)

            // Adding downloaded data into Local Array
            Currencies = downloadedCurrencies

        } catch let jsonErr {
            print("Here! Error serializing json", jsonErr)
        }

        }.resume()
}

バックグラウンドアプリの更新を実装するために、次の関数をAppDelegateに追加しました。

var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.

    // Background App Refresh Config
    UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
    return true
}

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    if let VC = window?.rootViewController as? SearchVC {
        // Update JSON data
        VC.downloadJSON()
        completionHandler(.newData)
    }
}

ただし、シミュレーターでバックグラウンドアプリの更新をシミュレートすると、次の警告が表示されます。

警告:アプリケーションデリゲートは-application:performFetchWithCompletionHandler:の呼び出しを受け取りましたが、完了ハンドラーは呼び出されませんでした。

完了ハンドラーを実装する場所と方法は?

ありがとうございました

5
Vetuka

ダウンロードコードをViewControllerから別のクラスに移動するか、少なくとも現在のバックグラウンド更新メソッドを変更して、必要に応じてViewControllerをインスタンス化する必要があります。アプリがフォアグラウンドで起動されていない場合、バックグラウンドリフレッシュがトリガーされる可能性があるため、if letは失敗します。

質問のコードを検討してください。

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    if let VC = window?.rootViewController as? SearchVC {
        // Update JSON data
        VC.downloadJSON()
        completionHandler(.newData)
    }
}

if let...が渡されない場合は、completionHandlerを呼び出さずに関数を終了するため、完了ハンドラーが呼び出されなかったというruntime警告が表示されます。

completionHandlerの場合にelseへの呼び出しを含めるようにコードを変更できますが、この場合、フェッチは行われません。

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    if let VC = window?.rootViewController as? SearchVC {
        // Update JSON data
        VC.downloadJSON()
        completionHandler(.newData)
    } else {
        completionHandler(.noData)
} 

または、必要に応じてView Controllerをインスタンス化することもできます(または別のデータフェッチクラスをお勧めします)。

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    let vc = (window?.rootViewController as? SearchVC) ?? SearchVC()
        // Update JSON data
    vc.downloadJSON()
    completionHandler(.newData)
}

また、downloadJSON関数を変更して、JSONのダウンロードが完了したときに呼び出す完了ハンドラー引数を含める必要があります。これにより、実際にデータをダウンロードした後、バックグラウンドフェッチ完了ハンドラーを呼び出すことができます。

func downloadJSON(completion: ((Bool,Error?) -> Void )? = nil)) {

    guard let url = URL(string: "myURL") else { 
        completion?(false, nil)
        return 
    }

    URLSession.shared.dataTask(with: url) { (data, response, err) in

        guard nil == err else {
            completion?(false, err)
            return
        }

        guard let data = data else { 
            completion?(false, nil)
            return 
        }

        do {
            let downloadedCurrencies = try JSONDecoder().decode([Currency].self, from: data)

            // Adding downloaded data into Local Array
            Currencies = downloadedCurrencies
            completion(true,nil)
        } catch let jsonErr {
            print("Here! Error serializing json", jsonErr)
            completion?(false,jsonErr)
        }

        }.resume()
}


func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    let vc = (window?.rootViewController as? SearchVC) ?? SearchVC()
        // Update JSON data
    vc.downloadJSON() { (newData,error) in
        if let err = error {
           NSLog("Background fetch error: \(err.localizedDescription)")
           completionHandler(.fail)
        } else {
            completionHandler(newData ? .newData:.noData)
        }
    }
}

2019年9月の更新

IOS 13では、新しいバックグラウンドフェッチおよび処理機能が導入されていることに注意してください。詳細については、これを参照してください WWDCセッション

6
Paulw11

これはおそらく、else-caseでcompletionHandlerを呼び出さないためです(これは決して発生しませんが、コンパイラーは認識しません)。

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    if let VC = window?.rootViewController as? SearchVC {
        // Update JSON data
        VC.downloadJSON()
        completionHandler(.newData)
    } else {
        completionHandler(.failed)
    }
}
3
cldrr