I have a class that runs similar to the AFHTTPSessionManager component of this tutorial http://www.raywenderlich.com/59255/afnetworking-2-0-tutorial
However, [self.tableView reloadData] is not working for me.
I have the manager implemented as so:
-(void) refresh{
manager = [[AFHTTPSessionManager...] iniwithBaseURL:...];
[manager Get:... parameters:... success:^(NSURLSessionDataTask *task, id responseObject){
//test success values in responseObject
if(test){
//Get table data
[self.tableView reloadData];
}
}
....
}
However if I run [self.tableView reloadData] in a separate function afterwards, it works just fine.
Why is this happening, instead of how it should in the tutorial?
Always reload on the main queue:
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
write the [self.tableView reloadData];in the main queue.
dispatch_sync(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
Related
I have a UITableView and a Refresh button in order to get new data from the server (if any).
[self startUpdates]; // animations
[[User myUser] getDataFromServer]; //async
[[User myUser] refreshElements:[[[UpdateContext alloc] initWithContext:data_ with:self with:#selector(endUpdates)] autorelease]];
[self.tableView reloadData];
The code above does not work well, because the getDataFromServer is an asynchronous method that is completed when the server returns the new data (the response). I want to be 100% sure that the refreshElements is being executed only when getDataFromServer gets the response back.
The question is: what is the correct way to do this. I want line 3 to gets executed if and only if line 2 gets the response from the server. Any ideas?
The easiest way would be to change the getDataFromServer method to accept a block that will contain the code that needs to be executed after the data comes from the server. You should ensure that the block will be executed in the Main thread.
Here is an example:
Changed method:
- (void)getDataFromServer:(void (^)(NSError * connectionError, NSDictionary *returnData))completionHandler{
//perform server request
//...
//
NSDictionary * data; //the data from the server
NSError * connectionError; //possible error
completionHandler(connectionError, data);
}
And how to call the new method with the block:
[self getDataFromServer:^(NSError *connectionError, NSDictionary *returnData) {
if (connectionError) {
//there was an Error
}else{
//execute on main thread
dispatch_async(dispatch_get_main_queue(), ^{
[[User myUser] refreshElements:[[[UpdateContext alloc] initWithContext:data_ with:self with:#selector(endUpdates)] autorelease]];
[self.tableView reloadData];
});
}
}];
I want to search the AppStore based on what a user types into search.
I have set up the following code to do this, which will amend the search on each character being entered to narrow the search.
However, as there is a request made as every character entered and these can take time to return, the UI can become unresponsive.
I would like to a) understand how I can stop the UI becoming unresponsive (I fear I am bringing the running back onto the main thread with performselectoronmainthread?), and b) would it be prudent to cancel the previous lookup as each character is entered, thus using the new narrower search, and if so how to do this?
Thanks in advance.
Update: I have tried the suggestion as made by Emilie Lessard, and whilst I can see the logic, I am unable to get this to benefit the app. See response below.
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)
-(void)searchBar:(UISearchBar*)searchBar textDidChange:(NSString*)text
{
if(text.length == 0)
{
jsonResults = nil;
[self.tableView reloadData];
}
else
{
jsonResults = nil;
[self.tableView reloadData];
NSURL *searchUrl = [NSURL URLWithString:[NSString stringWithFormat:#"https://itunes.apple.com/search?term=%#&country=gb&entity=software",text]];
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL:searchUrl];
[self performSelectorOnMainThread:#selector(fetchedData:)
withObject:data waitUntilDone:NO];
});
}
}
-(void)fetchedData:(NSData *)responseData{
NSError* error;
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
jsonResults = [json objectForKey:#"results"];
[self.tableView reloadData];
}
You need to use dispatch_async()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Do the computing-research
dispatch_async(dispatch_get_main_queue(), ^{
//do UI update here
});
});
By using a global queue, the UI of your app won't get blocked. Once all info has been computed/received, it is MANDATORY that you go back to the main thread (by using dispatch_async(dispatch_get_main_queue()) for all UI updates or you'll end up with hard-to-debug crashes.
I have an block to run to get a query set of data from a Azure Database :
[query readWithCompletion:^(NSArray *items, NSInteger totalCount, NSError *error) {
How can i get the *items and put it in a tableview ? As I cannot see this variable out of the block. I have tried to assign an external __ array in the block , but no use.
Has anyone tried to do this ?
thanks
Jason
i think you need some thing like this
[RSSParser parseRSSFeedForRequest:request success:^(NSArray *feedItems) {
self.linkArray=feedItems;//
dispatch_async(dispatch_get_main_queue(), ^{
//3
[self.tableView reloadData];
});
}
failure:^(NSError *error) { }];
The easiest way to see how this should work is to download the Quickstart application from the Windows Azure Portal after you create a Mobile Service. The quickstart is a Todo application that pulls down todo items you have added and displays them in a ListView. When you call your Mobile Service's read method, you specify a callback as seen here:
[query readWithCompletion:^(NSArray *results, NSInteger totalCount, NSError *error)
{
[self logErrorIfNotNil:error];
items = [results mutableCopy];
// Let the caller know that we finished
completion();
}];
In this method, a QSCompletionBlock called completion is called from the readWithCompletion method when it's received a response from your Mobile Service. In the Quickstart, that completion looks like this:
[self.todoService refreshDataOnSuccess:^
{
if (self.useRefreshControl == YES) {
[self.refreshControl endRefreshing];
}
[self.tableView reloadData];
}];
This then triggers the tableview to reload data. There are other methods that are part of the TableViewController class that are necessary to bind the data to the table view though so I'd highly recommend walking through the Quickstart code.
I am using the following code to invoke the [self.tableView reloadData] method inside the dispatch_async on the main thread. It works fine and as expected.
-(void) setup
{
_genres = [NSMutableArray array];
[[MyAppClient sharedClient] getGenres:^(NSURLSessionDataTask *task, id responseObject) {
[responseObject enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Genre *genre = [[Genre alloc] initWithDictionary:obj];
[_genres addObject:genre];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}];
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}];
}
Even if I use it without the dispatch_async call the UITableView reloads just fine. My question is that if there anyway benefit of dispatch_async in the above scenario.
My reason is that since I am updating the UI which runs on the main thread and that is why I am using dispatch_async(main_queue,block)
You always want to make sure that you do UI updates on the main thread, so you are right about doing the dispatch_async. One thing about your code, though: you're doing the dispatch_async to reload the table inside the block, so it is doing it for every single execution of the block. You only need to do it once, so I would suggest moving the dispatch_async to below the call to enumerateObjectsUsingBlock. This also ensures that you're not updating _genres on the background thread while the main thread is getting it to update the table.
You must update the UI on the main thread. If the response block is called on a background thread then your use of dispatch_async(dispatch_get_main_queue()... is correct as well as required.
I have a function that connects to the internet and then refreshes the cells in the table.
My function is:
- (void) updateMethod
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = #"Data request queue";
[queue addOperationWithBlock:^{
//neither of these delay the responsiveness of the table
[columnArrayBackground removeAllObjects];
[self getColumnDataBackground];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
for (int i=0; i < 6; i++) {
columnArray[i] = columnArrayBackground[i];
};
//THIS ONE LINE CAUSES DELAYS
[homeTable reloadData];
}];
}];
}
Everything is super speedy EXCEPT for [homeTable reloadData]. When I comment that out, I have quick response. When I uncomment it, my cell response lags sometimes by a few seconds!
My other reloadData calls do not delay my app. Am I not implementing NSOperationQueue correctly?
Remove the queues from your update method and leave only what updates the table. Under the action that refreshes your table add this code. Where the updateMethod is the code that updates the table. Only when you are back on the main thread do you reload the data. Hope this helps!
//perform on new thread to avoid the UI from freezing
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
^{
//background thread code
[self performSelector:#selector(updatemethod:) withObject:nil];
dispatch_async(dispatch_get_main_queue(),
^{ //back on main thread
[self.tableView reloadData];
});});