web-dev-qa-db-ja.com

iOSバックグラウンドアプリフェッチが機能しない問題のトラブルシューティング方法

IOSバックグラウンドアプリフェッチをアプリで動作させようとしています。 Xcodeでテストしている間は動作しますが、デバイスで実行すると動作しません!

  • テストデバイスでiOS 9.3.5を実行しています(展開ターゲットは7.1)
  • Xcodeのターゲットの「機能」の下にある「バックグラウンドモード」の下にある「バックグラウンドフェッチ」を有効にしました

enter image description here

Application:didFinishLaunchingWithOptionsでは、UIApplicationBackgroundFetchIntervalMinimumを含むsetMinimumBackgroundFetchIntervalでさまざまな間隔を試しました

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{

    // tell the system we want background fetch
    //[application setMinimumBackgroundFetchInterval:3600]; // 60 minutes
    [application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
    //[application setMinimumBackgroundFetchInterval:1800]; // 30 minutes

    return YES;
}

Application:performFetchWithCompletionHandlerを実装しました

void (^fetchCompletionHandler)(UIBackgroundFetchResult);
NSDate *fetchStart;

-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    fetchCompletionHandler = completionHandler;

    fetchStart = [NSDate date];

    [[NSUserDefaults standardUserDefaults] setObject:fetchStart forKey:kLastCheckedContentDate];
    [[NSUserDefaults standardUserDefaults] synchronize];

    [FeedParser parseFeedAtUrl:url withDelegate:self];
}

 -(void)onParserFinished
{
    DDLogVerbose(@"AppDelegate/onParserFinished");

    UIBackgroundFetchResult result = UIBackgroundFetchResultNoData;

    NSDate *fetchEnd = [NSDate date];
    NSTimeInterval timeElapsed = [fetchEnd timeIntervalSinceDate:fetchStart];
    DDLogVerbose(@"Background Fetch Duration: %f seconds", timeElapsed);
    if ([self.mostRecentContentDate compare:item.date] < 0) {
        DDLogVerbose(@"got new content: %@", item.date);
        self.mostRecentContentDate = item.date;
        [self scheduleNotificationWithItem:item];
        result = UIBackgroundFetchResultNewData;
    }
    else {
        DDLogVerbose(@"no new content.");
        UILocalNotification* localNotification = [[UILocalNotification alloc] init];
        localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:60];
        localNotification.alertBody = [NSString stringWithFormat:@"Checked for new posts in %f seconds", timeElapsed];
        localNotification.timeZone = [NSTimeZone defaultTimeZone];
        [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
    }

    fetchCompletionHandler(result);
}
  • XcodeのDebug/SimulateBackgroundFetchを使用して、シミュレーターとデバイスで(正常に!)テストしました。

  • 別のSO answer( https://stackoverflow.com/a/29923802/5190 )に示されているように、新しいスキームでテストに成功しました。

  • 私のテストでは、約0.3秒でperformFetchメソッドで実行されているコードを示しています(時間がかかりません)。
  • デバイスの設定でバックグラウンド更新が有効になっていることを確認しました。
  • もちろん、私は他のSO他の誰かが同じことを経験したことを願う質問を見てきました。:)

Xcodeに接続されていないデバイスで実行すると、コードが実行されません。アプリを開いて、アプリを閉じて(アプリを強制終了しませんでした!)、何時間も何日も待ちました。フェッチハンドラにログインしてみました。また、ローカル通知を送信するコードを書きました。

デバイスでローカル通知テストを正常に確認したことがあります。実際、iOSはフェッチを3回、それぞれ約15分間隔でトリガーするように見えましたが、その後再び発生することはありませんでした。

バックグラウンドフェッチの発生を許可する頻度を決定するために使用されるアルゴリズムは謎ですが、少なくとも数日のうちに時々実行されると予想されます。

他に何をテストするのか、どうしてそれがシミュレーターでは機能するがデバイスでは機能しないのかをトラブルシューティングする方法に困惑しています。

アドバイスを感謝します!

29
Jason

問題は、ネットワークフェッチ操作がバックグラウンドで発生し、デリゲートメソッドで完了ハンドラーを呼び出すため、完了ハンドラーを呼び出す前にperformFetchWithCompletionHandlerから戻ることです。 iOSはルールに従っていないと判断するため、バックグラウンドフェッチを使用する機能を拒否します。

問題を修正するには、beginBackgroundTaskWithExpirationHandlerを呼び出して、完了ハンドラーを呼び出した後にそのタスクを終了する必要があります。

何かのようなもの:

UIBackgroundTaskIdentifier backgroundTask

-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    fetchCompletionHandler = completionHandler;

    fetchStart = [NSDate date];

    self.backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^{
        [application endBackgroundTask:self.backgroundUpdateTask];
        self.backgroundTask = UIBackgroundTaskInvalid;
    }];

    [[NSUserDefaults standardUserDefaults] setObject:fetchStart forKey:kLastCheckedContentDate];

    [FeedParser parseFeedAtUrl:url withDelegate:self];
}

-(void)onParserFinished
{
    DDLogVerbose(@"AppDelegate/onParserFinished");

    UIBackgroundFetchResult result = UIBackgroundFetchResultNoData;

    NSDate *fetchEnd = [NSDate date];
    NSTimeInterval timeElapsed = [fetchEnd timeIntervalSinceDate:fetchStart];
    DDLogVerbose(@"Background Fetch Duration: %f seconds", timeElapsed);
    if ([self.mostRecentContentDate compare:item.date] < 0) {
        DDLogVerbose(@"got new content: %@", item.date);
        self.mostRecentContentDate = item.date;
        [self scheduleNotificationWithItem:item];
        result = UIBackgroundFetchResultNewData;
    }
    else {
        DDLogVerbose(@"no new content.");
        UILocalNotification* localNotification = [[UILocalNotification alloc] init];
        localNotification.alertBody = [NSString stringWithFormat:@"Checked for new posts in %f seconds", timeElapsed];
        [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
    }
    fetchCompletionHandler(result);
    [[UIApplication sharedApplication] application endBackgroundTask:self.backgroundUpdateTask];
    self.backgroundTask = UIBackgroundTaskInvalid;
}

このアプローチを使用する私のテストアプリは、最初は15分ごとにフェッチを実行していましたが、時間が経つにつれて頻度は少なくなります。バックグラウンドタスクがなければ、同じ問題が発生します。

バックグラウンドフェッチ間隔をUIApplicationBackgroundFetchIntervalMinimum以外に設定することも役立つことがわかりました。私のテストアプリは3600(1時間)のバックグラウンドフェッチ間隔で実行されており、数日間は確実にトリガーされています。電話を再起動してもアプリを再度実行しなくても。ただし、実際のトリガー間隔は2〜3時間です。

私のサンプルアプリは こちら です

27
Paulw11

バックグラウンドフェッチを実装するには、3つのことを行う必要があります:

  • アプリの機能のバックグラウンドモードで[バックグラウンドフェッチ]ボックスをオンにします。
  • SetMinimumBackgroundFetchInterval(_ :)を使用して、アプリに適した時間間隔を設定します。
  • バックグラウンドフェッチを処理するには、アプリデリゲートにapplication(_:performFetchWithCompletionHandler :)を実装します。

背景フェッチ頻度

アプリケーションがバックグラウンドフェッチを実行できる頻度はシステムによって決定され、次の条件によって異なります。

  • 特定の時間にネットワーク接続が利用可能かどうか
  • デバイスが起動しているか、つまり実行中かどうか
  • 前のバックグラウンドフェッチでアプリケーションが消費した時間とデータの量

言い換えれば、あなたのアプリケーションはあなたのためにバックグラウンドフェッチをスケジュールするために完全にシステムの容赦にあります。また、アプリケーションがバックグラウンドフェッチを使用するたびに、プロセスを完了するのに最大30秒かかります。

AppDelegateに含める(取得ニーズに合わせて以下のコードを変更)

-(void) application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {

    NSLog(@"Background fetch started...");

    //---do background fetch here---
    // You have up to 30 seconds to perform the fetch

    BOOL downloadSuccessful = YES;

    if (downloadSuccessful) {
        //---set the flag that data is successfully downloaded---
        completionHandler(UIBackgroundFetchResultNewData);
    } else {
        //---set the flag that download is not successful---
        completionHandler(UIBackgroundFetchResultFailed);
    }

    NSLog(@"Background fetch completed...");
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
    return YES;
}

アプリでバックグラウンド更新が有効になっているかどうかを確認するには、次のコードを使用します。

UIBackgroundRefreshStatus status = [[UIApplication sharedApplication] backgroundRefreshStatus];
switch (status) {
    case UIBackgroundRefreshStatusAvailable:
    // We can do background fetch! Let's do this!
    break;
    case UIBackgroundRefreshStatusDenied:
    // The user has background fetch turned off. Too bad.
    break;
    case UIBackgroundRefreshStatusRestricted:
    // Parental Controls, Enterprise Restrictions, Old Phones, Oh my!
    break;
}
12
TechSeeko