Search code examples
iosobjective-cobjective-c-blocksbackground-process

Block completion handler reference is nil after completing background fetch


I am trying to implement a background fetch of an RSS Feed using performFetchWithCompletionHandler, but when I want to call the completion handler it's nil.

Am I missing a way to retain my reference to self.completionHandler?

Am I declaring self.completionHandler correctly?

in app delegate:

        //background fetch new RSS Feeds
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    MasterViewController *navigationController = [mainStoryboard instantiateViewControllerWithIdentifier:@"MasterView"];
    MasterViewController *viewController = navigationController;

    [viewController startParsingWithCompletionHandler2: ^ (UIBackgroundFetchResult completionHandler2){
        completionHandler (UIBackgroundFetchResultNewData);
    }];
}

in main view controller:

@property (nonatomic, strong) void (^completionHandler)(UIBackgroundFetchResult);


- (void) startParsingWithCompletionHandler2:(void (^)(UIBackgroundFetchResult))completionHandler2
{
    self.completionHandler = completionHandler2;
    if (self.completionHandler) {
        NSLog(@"completionHandler");
    }else{
        NSLog(@"not completionHandler");
    }
    [self performSelector: @selector(stopParsing) withObject: nil afterDelay: PARSER_TIME_LIMIT];
    [self.activityIndicator startAnimating];
    numberOfCompletedStories = 0;
    [self.parserArray removeAllObjects];
                                                        //check for RSS Site data updates
    for (int lCounter = 0; lCounter < self.rssFeedAddresses.count; lCounter ++) {
        RSSParser *parser = [[RSSParser alloc] init];
        [parser setDelegate: self];
        [self.parserArray addObject: parser];
        [parser setSiteTitle: [self.rssFeedNames objectAtIndex: lCounter]];
        [NSThread detachNewThreadSelector: @selector(begin:) toTarget: parser withObject: [self.rssFeedAddresses objectAtIndex: lCounter]];
    }
    if (self.completionHandler) {
        NSLog(@"#2  completionHandler");
    }else{
        NSLog(@"#2  not completionHandler");
    }
}

    - (void) storyIsDone//called when parser completed one rss feed
{
    numberOfCompletedStories ++;
    if (self.completionHandler) {
        NSLog(@"storyIsDone  YES completion handler %i", numberOfCompletedStories);
    }else{
        NSLog(@"storyIsDone  Not completion handler");
    }
    if (numberOfCompletedStories == self.rssFeedAddresses.count)
    {
            //if all the feeds are done cancel time-out timer
        [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(stopParsing) object: nil];
        [self.activityIndicator stopAnimating];
        [self.refreshControl endRefreshing];
        [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(reloadRSSfeeds) name: @"ReloadFeeds" object: nil];
        canRefresh = YES;
        NSLog(@"call back");
        [self performSelectorOnMainThread: @selector(callCompletion) withObject: self waitUntilDone: YES];
    }//else not yet complete
}

- (void) callCompletion
{
    if (self.completionHandler) {
        NSLog(@"callCompletion  YES completion handler");
        self.completionHandler (UIBackgroundFetchResultNewData);

    }else{
        NSLog(@"callCompletion  Not completion handler");
    }
}

The output is:

completionHandler
 #2  completionHandler
storyIsDone  Not completion handler
storyIsDone  Not completion handler
storyIsDone  Not completion handler
storyIsDone  Not completion handler
storyIsDone  Not completion handler
storyIsDone  Not completion handler
storyIsDone  Not completion handler
call back
callCompletion  Not completion handler

Solution

  • Class not properly initialized

    When using performFetchWithCompletionHandler for background fetch - methods are called in a different order resulting in some objects not being properly initialized.

    When the app is launched in the foreground these methods are called: (in order)

    initWithCoder awakeFromNib viewDidLoad dispatchLoadingOperation viewDidAppear

    When performing a background fetch methods are called in the following order:

    initWithCoder awakeFromNib startParsingWithCompletionHandler2 viewDidLoad dispatchLoadingOperation viewDidAppear

    Of particular note: vieDidLoad was called before dispatchLoadingOperation which kicked off the parsing when running in foreground.

    When running in background, startParsingWithCompletionHandler2 (which also kicks off parsing when running in background) was called before viewDidLoad.

    Since several objects were initialized in viewDidLoad, the parsing was begun before expected and my array was not initialized to store my parsing results. This appeared to me that the app was not launching.

    While I was looking at the call back for the completion handler being nil, the real issue was the Class not being setup properly.