完成后台获取后,块完成处理程序引用为 nil

Block completion handler reference is nil after completing background fetch

我正在尝试使用 performFetchWithCompletionHandler 实现 RSS Feed 的后台获取,但是当我想调用完成处理程序时它是零。

我是否缺少保留对 self.completionHandler 的引用的方法?

我声明 self.completionHandler 正确吗?

在应用委托中:

        //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);
    }];
}

在主视图控制器中:

@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");
    }
}

输出为:

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

我总是使用 (nonatomic, copy) 作为块属性。您可以尝试一下,看看是否有帮助。

不过,在您的代码中,我认为您可以只传递传递给 application:performFetchWithCompletionHandler: 的完成处理程序。

在 .h 文件中定义一个完成处理程序,如下所示:

typedef void (^CompletionHandler)(UIBackgroundFetchResult BFR);

也像这样定义完成处理程序属性:

@property (copy) CompletionHandler completionHandler;

然后在.m文件中这样设置:

self.completionHandler = handler;

这里需要(copy)关键字,以便复制通过startParsingWithCompletionHandler2:方法传递的块,然后该副本由主视图控制器保留。

来自文档:

Note: You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior.

this link

编辑:

我认为当 completionHandlernil。如果你这样做会导致你的应用程序崩溃。

为了克服这个问题,您可以进行简单的检查,例如

!completionHandler ?: completionHandler(/*someArgs*/);

以上代码在语义上等同于:

if (completionHandler != nil) {
    completionHandler(/*someArgs*/)
}

它只是使用三元运算符将其压缩到一行。

Class 未正确初始化

使用 performFetchWithCompletionHandler 进行后台提取时 - 方法的调用顺序不同,导致某些对象未正确初始化。

当应用程序在前台启动时,将调用这些方法:(按顺序)

initWithCoder 唤醒笔尖 viewDidLoad 调度加载操作 viewDidAppear

执行后台提取时,按以下顺序调用方法:

initWithCoder 唤醒笔尖 startParsingWithCompletionHandler2 viewDidLoad 调度加载操作 viewDidAppear

特别注意:vieDidLoad 是在 dispatchLoadingOperation 之前调用的,它在 运行 在前台时开始解析。

当 运行 在后台时,在 viewDidLoad 之前调用 startParsingWithCompletionHandler2(当 运行 在后台时也会开始解析)。

由于在viewDidLoad中初始化了几个对象,所以解析比预期的要早,我的数组没有初始化来存储我的解析结果。在我看来,应用程序没有启动。

当我查看完成处理程序的回调为 nil 时,真正的问题是 Class 没有正确设置。