简体   繁体   中英

Multiple NSURLSessions Causing UITableView Problems

I'm running into a bit of a strange problem here. One of my NSURLSessions is in charge of getting information for restaurant information that I have stored (restaurant name, restaurant's logo URL, etc), and then the second NSURLSession is in charge of using the restaurant's logo URL to retrieve the specific image and set it for each UITableView's cell.

The problem, however, is that my UITableView does not load anything at all sometimes so the cells are empty, but at other times when I add an extra [_tableView reload] in the NSURLSessions' completion block in the fetchPosts method, it'll work, but then the cells will stop displaying anything again if I re-run it. Something is definitely wrong. Have a look at my code below:

#import "MainViewController.h"
#import "SWRevealViewController.h"
#import "RestaurantNameViewCell.h"
#import "RestaurantList.h"

@interface MainViewController ()

@end

@implementation MainViewController

- (void)viewDidLoad
{
    [super viewDidLoad];


    //List of restaurants needed to load home page
    _restaurantInformationArray = [[NSMutableArray alloc] init];


    self.tableView.dataSource = self;
    self.tableView.delegate = self;


    //setup for sidebar
    SWRevealViewController *revealViewController = self.revealViewController;
    if ( revealViewController )
    {
        [self.sidebarButton setTarget: self.revealViewController];
        [self.sidebarButton setAction: @selector( revealToggle: )];
        [self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
    }



    //Get list of restaurants and their image URLs
    [self fetchPosts];

}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [_restaurantInformationArray count];
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    RestaurantNameViewCell *cell = (RestaurantNameViewCell *)[_tableView dequeueReusableCellWithIdentifier:@"restaurantName" forIndexPath:indexPath];

    RestaurantList *currentRestaurant = [_restaurantInformationArray objectAtIndex:indexPath.row];


    cell.restaurantName.text = currentRestaurant.name;
    cell.imageAddress = currentRestaurant.imageURL;

    cell.restaurantClicked = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapDetected:)];
    cell.restaurantClicked.numberOfTapsRequired = 1;
    cell.restaurantLogo.userInteractionEnabled = YES;
    [cell.restaurantLogo addGestureRecognizer:cell.restaurantClicked];
    cell.restaurantLogo.tag = indexPath.row;

    //Add restaurant logo image:
    NSString *URL = [NSString stringWithFormat:@"http://private.com/images/%@.png",cell.imageAddress];
    NSURL *url = [NSURL URLWithString:URL];

    NSURLSessionDownloadTask *downloadLogo = [[NSURLSession sharedSession]downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
        UIImage *downloadedImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]];

        cell.restaurantLogo.image = downloadedImage;
    }];
    [downloadLogo resume];

    return cell;
}



-(void)fetchPosts {
    NSString *address = @"http://localhost/xampp/restaurants.php";
    NSURL *url = [NSURL URLWithString:address];

    NSURLSessionDataTask *downloadRestaurants = [[NSURLSession sharedSession]dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        NSError *someError;
        NSArray *restaurantInfo = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&someError];


         for(NSDictionary *dict in restaurantInfo) {
             RestaurantList *newRestaurant = [[RestaurantList alloc]init];

             newRestaurant.name = [dict valueForKey:@"name"];
             newRestaurant.imageURL = [dict valueForKey:@"image"];

             [_restaurantInformationArray addObject:newRestaurant];


             //Refresh table view to make sure the cells have info AFTER the above stuff is done
             [_tableView reloadData];
         }
    }];

    [downloadRestaurants resume];
}


@end

It's probably a very stupid mistake that I'm making, but I'm not certain how I should correct this. I'm new to iOS development, so would greatly appreciate some guidance :)

Besides assuming that your network requests aren't erroring (you should at least log if there are network errors), there are threading issues.

Your NSURLSession callback probably runs on a background thread. This makes it unsafe to call UIKit (aka - [_tableView reloadData] ). UIKit isn't thread safe. This means invoking any of UIKit's APIs from another thread creates non-deterministic behavior. You'll want to run that piece of code on the main thread:

dispatch_async(dispatch_get_main_queue(), ^{
    [_tableView reloadData];
});

Likewise for fetching the images. It's slightly more complicated because of table view cell reuse which could cause the wrong image to display when scrolling. This is because the same cell instance is used for multiple values in your array as the user scrolls. When any of those callbacks trigger, it'll replace whatever image happens to be in that cell. The generally steps to reproduce this is as follows:

  1. TableView requests 5 cells
  2. MainViewController requests 5 images (one for each cell)
  3. User scrolls down one cell
  4. The first cell gets reused as the 6th cell.
  5. MainViewController requests another image for the 6th cell.
  6. The 6th image is retrieved, the callback is triggered, image of the first cell is set to image #6.
  7. The 1st image is retrieved, the callback is triggered, image of the first cell is set to image #1 ( incorrect ).

You'll need to make sure the cell is displaying the correct cell before attempting to assign the image to it. If you rather not implement that logic for image fetching in cells, you could use SDWebImage instead. Using SDWebImage's [UIImageView sd_setImageWithURL:] is thread safe (it will set the image on the main thread ).

Side notes:

  • You only need to reload data once all your changes are in _restaurantInformationArray , and not every time in the for loop.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM