読者です 読者をやめる 読者になる 読者になる

ほげほげ(仮)

仮死状態

バックグラウンドでNSURLSessionDownloadTaskのdelegateが呼ばれない時の対応

Objective-C

Remote Notification通知受信後にバックグラウンドでAPIからデータを取得しようとしたのですが、NSURLSessionDownloadTaskの完了delegateが呼ばれずにハマった時のメモです。

全体的なサンプルは Multitasking in iOS 7 - iOS 7 - objc.io issue #5 を参考に。

解決策はAppDelegateapplication:handleEventsForBackgroundURLSession:completionHandler:を実装してあげるだけでした。

ここの箇所を見逃していたせいでずっとハマっていました。

AppDelegate.h

NSURLSessionDelegateを追加します。

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate, NSURLSessionDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

AppDelegate.m

必要そうな箇所だけ記述。

// 完了ハンドラの定義
typedef void (^CompletionHandlerType)();

@interface AppDelegate ()
// identifierごとに完了ハンドラを保存
@property NSMutableDictionary *completionHandlerDictionary;
@end

@implementation AppDelegate

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

    self.completionHandlerDictionary = [NSMutableDictionary dictionaryWithCapacity:0];

    return YES;
}

...

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    // ここでNSURLSessionDownloadTaskの処理
    ...

    completionHandler(UIBackgroundFetchResultNewData);
}

- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
{
    // NSURLSessionDownloadTaskのdelegateが呼ばれる前にここが呼ばれる
    // これが実装されていないとdelegateが実行されない

    [self addCompletionHandler:completionHandler forSession:identifier];
}

#pragma mark - NSURLSessionDelegate
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
    // NSURLSessionDownloadTaskのdelegate実行後に呼ばれる
    // ここでOSに完了を通知

    if (session.configuration.identifier) {
        [self callCompletionHandlerForSession:session.configuration.identifier];
    }
}

#pragma mark - Private methods
- (void)addCompletionHandler:(CompletionHandlerType)handler forSession:(NSString *)identifier
{
    // 完了ハンドラを保存しておく
    self.completionHandlerDictionary[identifier] = handler;
}

- (void)callCompletionHandlerForSession: (NSString *)identifier
{
    // 完了ハンドラを取得
    CompletionHandlerType handler = self.completionHandlerDictionary[identifier];
    if (handler) {
        [self.completionHandlerDictionary removeObjectForKey:identifier];
        // OSに完了を通知
        handler();
    }
}

補足

AFNetworkingAFURLSessionManagerを使って同様のことをする場合も上記のAppDelegateの実装が必要なります。