IOSバックグラウンドアプリフェッチをアプリで動作させようとしています。 Xcodeでテストしている間は動作しますが、デバイスで実行すると動作しません!
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 )に示されているように、新しいスキームでテストに成功しました。
Xcodeに接続されていないデバイスで実行すると、コードが実行されません。アプリを開いて、アプリを閉じて(アプリを強制終了しませんでした!)、何時間も何日も待ちました。フェッチハンドラにログインしてみました。また、ローカル通知を送信するコードを書きました。
デバイスでローカル通知テストを正常に確認したことがあります。実際、iOSはフェッチを3回、それぞれ約15分間隔でトリガーするように見えましたが、その後再び発生することはありませんでした。
バックグラウンドフェッチの発生を許可する頻度を決定するために使用されるアルゴリズムは謎ですが、少なくとも数日のうちに時々実行されると予想されます。
他に何をテストするのか、どうしてそれがシミュレーターでは機能するがデバイスでは機能しないのかをトラブルシューティングする方法に困惑しています。
アドバイスを感謝します!
問題は、ネットワークフェッチ操作がバックグラウンドで発生し、デリゲートメソッドで完了ハンドラーを呼び出すため、完了ハンドラーを呼び出す前に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時間です。
私のサンプルアプリは こちら です
バックグラウンドフェッチを実装するには、3つのことを行う必要があります:
背景フェッチ頻度
アプリケーションがバックグラウンドフェッチを実行できる頻度はシステムによって決定され、次の条件によって異なります。
言い換えれば、あなたのアプリケーションはあなたのためにバックグラウンドフェッチをスケジュールするために完全にシステムの容赦にあります。また、アプリケーションがバックグラウンドフェッチを使用するたびに、プロセスを完了するのに最大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;
}