You said:
When app launch: - URL number 1 is loaded; - URL number 1 is showed; When I click one time right: - URL number 2 is loaded; - Nothing happen; When I click one more time right: - URL number 3 is loaded; - URL number 2 is showed; When I click one time left: - URL 2 is loaded; - URL 3 is showed; When I click one more time left: - URL 1 is loaded; - URL 2 is showed;
From this, we know:
- URL/XML loading works—the data is there to be displayed, but the display isn't happening when it should.
- Table reloading works—on a later button press, the earlier data is displayed, but there is different data being displayed.
- Table reloading is happening too early—that's why it's showing the previous URL/XML's data.
Your button actions are (indirectly) making the URL-loading call using a library that takes success/failure block(s), so here's what's happening, roughly:
- The IBAction calls
loadUrl:
, which runs TBXML'sinitWithURL:success:failure:
initWithURL:success:failure:
stores the success and failure blocks and starts the actual network action on a separate thread ("in the background"), and returns, almost certainly before the network action has finished.- The IBAction calls
reloadData
. - Later, in the background, the network action finishes and the success or failure block is run.
So, moving the reloadData
call into the success block makes it so that the table is reloaded after the XML data is processed.
But initially this crashed with an error that included:
This may be a result of calling to UIKit from a secondary thread.
UIKit calls—roughly, any method call that affects the user interface—have to be made on the main thread. For some asynchronous libraries that use block callbacks (notably AFNetworking), the block callbacks are guaranteed to be run on the main thread for just this reason. With TBXML, apparently, the block callbacks are run in the background, probably on the same thread that did the actual network loading action. So, we need some way to make the reloadData
call to the UITableView
happen on the main thread.
There are several ways to do this. My first preference is usually to use the main operation queue (which we get using the mainQueue
class method on NSOperationQueue
), which is a queue of operations to perform on the main thread:
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[_tableView reloadData];
}];
The main operation queue then automatically handles executing the block as soon as it can on the main thread. Alternately, but still at the Objective-C/Cocoa/CocoaTouch level, you can use performSelectorOnMainThread:withObject:waitUntilDone:
on your table view:
[_tableView performSelectorOnMainThread:@selector(reloadData)
withObject:nil
waitUntilDone:NO];
(or YES
for wait until done—probably doesn't matter in this case), which has exactly the same effect. A third option, though generally not preferred in this case, is to use Grand Central Dispatch directly:
dispatch_async(dispatch_get_main_queue(), ^{
[_tableView reloadData];
}];
(but when there are higher-level options like the NSOperationQueue
route or the performSelectorOnMainThread:withObject:waitUntilDone:
route, they should be used instead of this kind of lower-level C Grand Central Dispatch routine; also, changing dispatch_async
to dispatch_sync
has the same effect as changing waitUntilDone:NO
to waitUntilDone:YES
in the previous example).