2

I'm using TBXML+HTTP to get XML data from a website. What I want to do is populate a UITableView with the processed data. I've created a NSMutableArray that holds all the entries and, as far as that goes, everything is working fine. The problem is when, after successfully fetching the data from the server and storing it in the array, I try to update the table to show the data, using reloadData.

It takes about 5 seconds to populate the table with 20 rows. The weird part is that the fetching is happening really fast, so the data is available right away. I do not understand what is taking so long. Here is some info from the log:

2012-03-17 18:46:01.045 MakeMyApp[4571:207] numberOfSectionsInTableView: 1
2012-03-17 18:46:01.047 MakeMyApp[4571:207] numberOfRowsInSection: 0
2012-03-17 18:46:01.244 MakeMyApp[4571:1f03] numberOfSectionsInTableView: 1
2012-03-17 18:46:01.245 MakeMyApp[4571:1f03] numberOfRowsInSection: 20
2012-03-17 18:46:01.245 MakeMyApp[4571:1f03] Ok, I'm done. 20 objects in the array.
2012-03-17 18:46:01.246 MakeMyApp[4571:1f03] Finished XML processing.
2012-03-17 18:46:06.197 MakeMyApp[4571:1f03] cellForRowAtIndexPath:

As you can see, it fires the pair numberOfSectionsInTableView:/numberOfRowsInSection: two times: the first is when the view loads, the second is when I do [self.tableView reloadData];

You see that, in less than a second, the array is populated with the 20 objects and all the work processing the XML is done. How is cellForRowAtIndexPath: fired only 5 seconds later?

Here are some parts of the code that might help finding the problem:

- (void)createRequestFromXMLElement:(TBXMLElement *)element {
    // Let's extract all the information of the request
    int requestId = [[TBXML valueOfAttributeNamed:@"i" forElement:element] intValue];

    TBXMLElement *descriptionElement = [TBXML childElementNamed:@"d" parentElement:element];
    NSString *description = [TBXML textForElement:descriptionElement];

    TBXMLElement *statusElement = [TBXML childElementNamed:@"s" parentElement:element];
    int status = [[TBXML textForElement:statusElement] intValue];

    TBXMLElement *votesCountElement = [TBXML childElementNamed:@"v" parentElement:element];
    int votesCount = [[TBXML textForElement:votesCountElement] intValue];    

    // Creating the Request custom object
    Request *request = [[Request alloc] init];

    request.requestId = requestId;
    request.description = description;
    request.status = status;
    request.votes_count = votesCount;

    [requestsArray addObject:request];
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

    appListCell *cell = (appListCell *)[tableView dequeueReusableCellWithIdentifier:@"appListCell"];

    Request *request = [requestsArray objectAtIndex:indexPath.row];

    cell.appIdLabel.text = [NSString stringWithFormat:@"#%d", request.requestId];
    cell.appTextLabel.text = request.description;
    cell.appVotesLabel.text = [NSString stringWithFormat:@"%d votes", request.votes_count];

    return cell;
}

Thanks a lot!

EDIT:

- (void)traverseXMLAppRequests:(TBXMLElement *)element {
    int requestCount = 0;
    TBXMLElement *requestElement = element->firstChild;

    // Do we have elements inside <re>?
    if ((requestElement = (element->firstChild))) {
        while (requestElement) {
            [self createRequestFromXMLElement:requestElement];

            requestCount++;

            requestElement = [TBXML nextSiblingNamed:@"r" searchFromElement:requestElement];
        }
    }

    [self.tableView reloadData];   

    NSLog(@"Ok, I'm done. %d objects in the array.", [requestsArray count]);

}

EDIT 2: I've tried fetching the information of just 1 row, instead of 20, and the delay is exactly the same.

user1276108
  • 183
  • 2
  • 7
  • What I'm finding interesting is that you're getting an NSLog for the cellForRowAtIndexPath: call, but you don't have any NSLogs in there. Did you 'clean up' your code, or do you actually have the call elsewhere? – RonLugge Mar 17 '12 at 19:37
  • I'm sorry, I should have make that more clear. I did clean up my code, because I tried so many things already that the code was a mess with a bunch of NSLogs and other tricks. – user1276108 Mar 17 '12 at 19:52
  • Perhaps you should also show the code for the method that includes the `reloadData` call. – jonkroll Mar 17 '12 at 19:59
  • I've just edited the question and added that method, traverseXMLAppRequests. Thanks. – user1276108 Mar 17 '12 at 22:51
  • Well, looking at your code the NSLog for 'OK I'm done' actually comes AFTER you hit reloadData. That makes me a tad bit suspicious about the timing on cellForRowAtIndexPath: but I'm going to have to go through some documentation before I can guess what's going on. – RonLugge Mar 18 '12 at 04:05
  • Just out of curiosity, have you tried replacing reload data with a series of insert row calls? – RonLugge Mar 18 '12 at 04:17
  • Exactly. The fact that the NSLog is after the reloadData is what confuses me. It makes no sense that the cellForRowAtIndexPath: takes so long to be executed. – user1276108 Mar 18 '12 at 15:33
  • As for the insert row instead of reloadData, I will try that right now. – user1276108 Mar 18 '12 at 15:33
  • Ok, I've tried inserting each row separately and delay is exactly the same. The rows appear at the table all at once, about 5 seconds later. – user1276108 Mar 18 '12 at 16:00

3 Answers3

16

I know this is a really old question, but I've just come across the exact same issue and think I might know the cause. Might help someone who stumbles across this same question...

I suspect that your reloadData call is happening on a background thread as opposed to the main thread. What you describe is the exact effect of this - the code runs but there is a long delay (5-10 secs) in updating the user interface.

Try changing the reloadData call to:

dispatch_async(dispatch_get_main_queue(), ^{
    [self.tableView reloadData];
});
CharlesA
  • 4,260
  • 2
  • 25
  • 31
8

You are calling reloadData on a thread other than the mainThread

Make sure to reload your tableView's data on main thread like in the below:

OBJ-C:

[self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];

Swift 3:

DispatchQueue.main.async {
   self.tableView.reloadData()
}
TechSeeko
  • 1,521
  • 10
  • 19
0

Where does:

- (void)createRequestFromXMLElement:(TBXMLElement *)element

get called? Sound like you might be making 20 web requests to get that file contents. That would definitely make things slow (as opposed to doing one request to get the entire XML file and parsing that locally).

Can you post your whole VC code?

LJ Wilson
  • 14,445
  • 5
  • 38
  • 62