Search code examples
objective-cjsonnsurl

NSURLsession &JSON causing very long delay in loading table view


I'm having a problem with my table view taking a very long time to load (sometimes around 20+ seconds). First off this has nothing to do with our server or internet connection, I've checked getting the data via another system and its working well. Please find below the code from my ViewDidLoad, you'll notice that there is an NSLOG printing out the contents of dataArray. To try and find where the problem is, I set two break points in my table view controller one the line after the NSLOG and the other at the start of the cellForRowAtIndexPath. When I run it, it gets to the first breakpoint and also shows the correct dataArray information almost immediately with no delay ie it has retrieved the JSON data very quickly. I stop the program and run it again, this time I click continue as soon as it stops at the first breakpoint, it takes 20+ seconds, before the second breakpoint is reached (I'm only loading 3 cells, so find this a little excessive). I am pretty new to this, so may well be making some newbee error. Either way any help would really be appreciated.

Thanks

//  Set up the session configuration
    NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];

    [[session dataTaskWithURL:[NSURL URLWithString:teacherUrl]
            completionHandler:^(NSData *data,
                                NSURLResponse *response,
                                NSError *error) {

                if (!error) {
                    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
                    if (httpResponse.statusCode == 200){

                        NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers|NSJSONReadingAllowFragments error:&error];

                        // move serialised JSON into Array (as the data contained is in array)
                        dataArray=(NSArray*)[jsonData copy];
                    NSLog(@"datarr == %@", dataArray);
                        [self.tableView reloadData];



                    }
                }
            }



      ]resume];

    }


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    // Return the number of rows in the section.


    return [dataArray count];

 }


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


// Load and display name
    UILabel *nameLabel = (UILabel *)[cell viewWithTag:100];
    nameLabel.text = [dataArray[indexPath.row] valueForKeyPath: @"firstName"];

// Load and display address
    UILabel *addressLabel = (UILabel *)[cell viewWithTag:101];
    NSString *city = [dataArray[indexPath.row] valueForKeyPath: @"address.city"];
    addressLabel.text = [NSString stringWithFormat:@"%@", city];

// Load and display rating star images
    NSNumber *rating = [dataArray[indexPath.row] valueForKeyPath: @"rating"];
    UIImageView *ratingImageView = (UIImageView *)[cell viewWithTag:102];
    ratingImageView.image = [self imageForRating:rating];

// Load and display subjects
    UILabel *subjectLabel = (UILabel *)[cell viewWithTag:103];
    NSArray *arr = [dataArray [indexPath.row] valueForKeyPath:@"subjects"];
    NSString * subject1String = [arr[0] valueForKeyPath: @"name"];
    NSString * subject2String = @" ";
    NSString * dots = @" ";
    if (arr.count >1) {
        subject2String = [arr[1] valueForKeyPath: @"name"];
        if (arr.count >2) {
            dots = @"...";
        }
    }

   subjectLabel.text = [NSString stringWithFormat:@"%@ %@ %@", subject1String, subject2String, dots];

// Load and display review
    UILabel *reviewLabel = (UILabel *)[cell viewWithTag:104];
    reviewLabel.text = [dataArray[indexPath.row] valueForKeyPath: @"hotReview"];

// Load and display Number of lessons
//    UILabel *noOfLessonsLabel = (UILabel *)[cell viewWithTag:105];
//    noOfLessonsLabel.text = [dataArray[indexPath.row] valueForKeyPath: @"lessons"];

// Load and display distance
    UILabel *distanceLabel = (UILabel *)[cell viewWithTag:106];
    //distanceLabel.text = [dataArray[indexPath.row] valueForKeyPath: @"firstName"];
    distanceLabel.text = @"1.5km";


// Load and display photo using SDWEBImage
    UIImageView *photoImageView = (UIImageView *)[cell viewWithTag:120];

    NSString *urlPhotoId = [dataArray [indexPath.row]valueForKeyPath:@"picture.id"];
    NSString *urlPhoto = [NSString stringWithFormat:@"http://soon.nextdoorteacher.com/img/profiles/%@.jpg", urlPhotoId];

    [photoImageView sd_setImageWithURL:[NSURL URLWithString:urlPhoto] placeholderImage:[UIImage imageNamed:@"mortarboard2"]];

    return cell;
}


- (UIImage *)imageForRating:(NSNumber*)rating
{

    if (rating.floatValue >=1 && rating.floatValue <2) {
        return [UIImage imageNamed:@"1starimage"];
    }else if (rating.floatValue  >=2 && rating.floatValue <3){
        return [UIImage imageNamed:@"2starimage"];
    }else if (rating.floatValue >=3 && rating.floatValue <4){
        return [UIImage imageNamed:@"3starimage"];
    }else if (rating.floatValue >=4 && rating.floatValue <5){
        return [UIImage imageNamed:@"4starimage"];
    }else if (rating.floatValue >=5){
        return [UIImage imageNamed:@"5starimage"];
    }else{
        return nil;
    }
}

Solution

  • ahh, @rmaddy is right (as is usual ;) these completion handlers aren't guaranteed to be on the main thread. I usually use the NSURLConnection method sendAsynchronousRequest:queue:completionHandler: which allows you to pass in the mainQueue to guarantee that the completion block will run on the main thread. You might want to switch to that if you don't want to add in GCD. The 'To reload table data on main thread' edit that RahulMishra added should work but as an alternative you could rewrite that method using NSURLConnection like this:

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:teacherUrl];
    [NSURLConnection
          sendAsynchronousRequest:request
                            queue:[NSOperationQueue mainQueue]
                completionHandler:^(NSURLResponse *response,
                                    NSData *data,
                                    NSError *connectionError) {
                    if (!connectionError) {
                        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
                        if (httpResponse.statusCode == 200){
    
                            NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers|NSJSONReadingAllowFragments error:&error];
    
                            // move serialised JSON into Array (as the data contained is in array)
                            dataArray=(NSArray*)[jsonData copy];
                        NSLog(@"datarr == %@", dataArray);
                            [self.tableView reloadData];
                        }
                    }
                }
        }