NSURLSessionDataTask 和线程

NSURLSessionDataTask and threading

我正在获取一个带有 NSURLSessionDataTask 的 JSON 提要,并填充一个 NSMutableArray,它位于一个共享存储中,它是一个单例。 NSMutableArray 可以通过 getter 将其转换为 NSArray 供外界访问。

getter 调用刷新方法轮询 JSON 提要并填充 NSMutableArray,如下所示:

- (NSArray *)articles
{

    if ([_articles count] == 0) {

        [self refreshArticles];

    }
    return _articles;
}

这是其中的一些方法:

NSURLRequest *request = [NSURLRequest requestWithURL:feedURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:4.0];

NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request
                                             completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){

                                                 if (!error && response != nil) {

                                                     // decode JSON and add resultant objects to _articles

                                                     dispatch_async(dispatch_get_main_queue(), ^{
                                                         NSLog(@"Updated feed");
                                                         [nc postNotificationName:@"MLNArticleStoreFeedDidUpdate" object:self];
                                                     });
                                                 } else if (response == nil) {
                                                     NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
                                                     [nc postNotificationName:@"MLNNetworkError" object:self];
                                                 }

                                             }];

[task resume];

这有效,但每次调用 getter 时,提要都会刷新 7 次。我认为这与 getter 的 if 子句在 feed 下载时继续为真有关。我已经用 dispatch_once 缓解了这个问题,它起作用了,但我觉得那不对。

现在的代码是这样的:

- (NSMutableArray *)articles
{

    if ([_articles count] == 0) {
        static dispatch_once_t onceToken;

        dispatch_once(&onceToken, ^{
            [self refreshArticles];
        });
    }
    return _articles;
}

但我的意思是:"if there are no articles, go get some, and then return"。有更好的方法吗?

dispatch_once,以这种方式使用,不会做你想做的事。这里的真实情况是,您几乎可以肯定 不想 想要等待网络 activity 后再返回。如果你像那样阻塞主线程,OS 会杀死你的应用程序。

- (NSArray *)articles
{    
    if ([_articles count] == 0) {
        [self refreshArticlesFromNetwork];
    }
    return _articles;
}

- (void)refreshArticlesFromNetwork
{
    if (self.networkRefreshInProgress)
        return;

    self.networkRefreshInProgress = YES;
    [self showNetworkLoadingUI];

    NSURLRequest *request = [NSURLRequest requestWithURL:feedURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:4.0];
    NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
        NSMutableArray* localArray = [NSMutableArray array];

        if (!error && response != nil) {
            // decode JSON and add resultant objects to local array
            [localArray addObject: ... ];
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            _articles = [localArray copy];
            self.networkRefreshInProgress = NO;
            [self hideNetworkLoadingUI];
            NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
            if (!error && response != nil) {
                [nc postNotificationName:@"MLNArticleStoreFeedDidUpdate" object:self];
            } else if (response == nil) {
                [nc postNotificationName:@"MLNNetworkError" object:self];
            }
            NSLog(@"Updated feed");
        });
    }];

    [task resume];
}

此处要点:

  • 向用户显示一些 "Loading..." UI 而不是阻塞主线程。
  • 如果等效更新已经在进行中,则不要触发其他更新。
  • 不要修改用于从后台线程填充 UI 的模型状态。