0

I’m getting a JSON feed with NSURLSessionDataTask and populating an NSMutableArray that is inside a shared store which is a singleton. The NSMutableArray is accessible to the outside world through a getter that casts it as an NSArray.

The getter calls a refresh method which polls the JSON feed and populates the NSMutableArray, like so:

- (NSArray *)articles
{

    if ([_articles count] == 0) {

        [self refreshArticles];

    }
    return _articles;
}

Here’s some of that method:

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];

This works, but every time the getter is called, the feed gets refreshed 7 times. I think this has to do with the getter's if clause continuing to be true while the feed downloads. I have mitigated this with dispatch_once, and it works but I feel like that's not right.

Here’s what the code is now:

- (NSMutableArray *)articles
{

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

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

But what I mean is: "if there are no articles, go get some, and then return". Is there a better way of doing this?

1 Answer 1

1

dispatch_once, used in this way, will not do what you are trying to do. The real thing here is that you almost certainly don't want to wait for the network activity before returning. If you block the main thread like that, the OS will kill your app.

- (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];
}

The key takeaways here:

  • Show some "Loading..." UI to the user instead of blocking the main thread.
  • Don't trigger additional updates if an equivalent update is already in progress.
  • Don't modify model state used to populate UI from background threads.
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks! In other words, use a boolean flag to change to an "updating" state?
I’ve implemented this and it works like a charm. I already had the loading UI but the boolean flag together with a temporary NSMutableArray are brilliant. Thanks a million.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.