Push通知を受けたらバックグラウンドで画像をダウンロードする時のまとめ
やりたい事
1. アプリがバックグラウンドで起動している時にPush通知を受け取る
2. バックグラウンドにいる間に、あるURLから画像をダウンロードしておく
3. 起動時には画像はダウンロード済みで、サクサク動いてうれしい!
UIBackgroundFetchResultNewDataではまった
Pushを受け取った後に、バックグラウンド処理をする為には、application:didReceiveRemoteNotification:fetchCompletionHandler:をAppDelegate.mに実装する。
以下、実装(一部抜粋)。
AppDelegate.m
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { if (application.applicationState == UIApplicationStateBackground) { // バックグラウンドで画像をダウンロードする用のClass ImageDownloadInBackground *imageDownloadInBackground = [[ImageDownloadInBackground alloc] init]; [imageDownloadInBackground downloadByPushInBackground]; completionHandler(UIBackgroundFetchResultNewData); } } - (void) application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler { // このメソッドを書いておくと、バックグラウンドでNSURLSessionが呼ばれていた場合、そのdelegate method(ダウンロードが完了した場合のcallbackとか)が一斉に呼び出される // 書いておかないと、アプリがforegroundになったときに一斉に呼ばれるので× }
ImageDownloadInBackground.h
#import <Foundation/Foundation.h> @interface ImageDownloadInBackground : NSObject <NSURLSessionDownloadDelegate> @end
ImageDownloadInBackground.m
- (void) downloadByPushInBackground:(NSDictionary *)transitionInfo { NSString *identifier = @"identifier"; NSURLSessionConfiguration *configuration; if ([[[UIDevice currentDevice] systemVersion] floatValue] >=8.0f) { configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier]; } else { configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:identifier]; } configuration.allowsCellularAccess = YES; NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; NSString *forceStringURL = [NSString stringWithFormat:@"%@", url]; NSURL *assetURL = [NSURL URLWithString:forceStringURL]; NSURLSessionDownloadTask *task = [session downloadTaskWithURL:assetURL]; [task resume]; } - (void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { NSData *downloadedData = [NSData dataWithContentsOfURL:location]; if ([downloadedData length] == 0) { return; } ~ donwloadedDataをごにょごにょにする処理 ~ }
これでうまくいくかと思いきや、completionHandler(UIBackgroundFetchResultNewData)をよんだ時点でデータの更新が正常に終わったと判断され、バックグラウンドの処理(NSURLSessionDownloadDelegateのdelegate methodたち)が呼ばれなくなってしまう。
completionHandlerをダウンロード終了後に呼ぶようにする
という事で、completionHandlerをImageDownloadInBackgroundクラスに渡して、ダウンロードが正常に終わってからUIBackgroundFetchResultNewDataを呼ぶように修正。
以下、変更点だけ。
AppDelegate.m
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { if (application.applicationState == UIApplicationStateBackground) { // バックグラウンドで画像をダウンロードする用のClass ImageDownloadInBackground *imageDownloadInBackground = [[ImageDownloadInBackground alloc] init]; // completionHandlerをimageDownloadInBackgroundの保持する配列に突っ込む [imageDownloadInBackground.completionHandlerArray addObject:completionHandler]; [imageDownloadInBackground downloadByPushInBackground]; // ここでは呼ばない // completionHandler(UIBackgroundFetchResultNewData); } }
ImageDownloadInBackground.h
completionHandlerを格納する為の配列を定義
#import <Foundation/Foundation.h> typedef void (^CompletionHandlerType)(); @interface ImageDownloadInBackground : NSObject <NSURLSessionDownloadDelegate> @property NSMutableArray *completionHandlerArray; @end
ImageDownloadInBackground.m
// 配列の初期化、completionHandlerを呼ぶ処理を追加
- (id) init { self = [super init]; if (self != nil) { _completionHandlerArray = [[NSMutableArray alloc] init]; } return self; } - (void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { NSData *downloadedData = [NSData dataWithContentsOfURL:location]; if ([downloadedData length] == 0) { return; } ~ donwloadedDataをごにょごにょにする処理 ~ CompletionHandlerType handler = _completionHandlerArray[0]; handler(UIBackgroundFetchResultNewData); [_completionHandlerArray removeAllObjects]; }
これでダウンロードが正常に完了するようになった。
めでたし。