Objective-c/iOS: setting status text in async function is slow - iphone

In my app I'm doing some communication with a remote server and as this might be slow I thought it would be a good idea to run that code asynchronously. I have my communication code in a block that I pass to dispatch_async. This code does the communication and when it's done it sets the text of a label. This last part is the problem. The text is set, but it occurs after a delay of a few seconds. This is my code.
- (void)doNetworkingTask {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Slow network task goes here.
// Slow network task done, notify the user.
[self.myLabel setText:#"task done."];
NSLog(#"task done.");
});
}
What happens here is that my network task completes, the NSLog-text is logged and after a couple of seconds, the text of the label is updated. My question is 1) why does the label text not update instantly? and 2) what is a proper way of doing what I want to do? (do the slow network task without blocking anything else, update the user through a text label once I'm done.)

UI updates must be on the main thread. Update your code to something like this:
- (void)doNetworkingTask {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Slow network task goes here.
// Slow network task done, notify the user.
dispatch_async(dispatch_get_main_queue(), ^{
[self.myLabel setText:#"task done."];
});
NSLog(#"task done.");
});
}

Related

iPhone iOS how to implement a sequence of background network send operations in the fastest way possible?

I'm trying to stream data to a server at regular intervals and do so in a way that is fast and does not block the UI. The UI is also pretty busy trying to display the data. Currently my implementation uses NSTimer that fires every 50ms, picks a network packet out of a circular array and sends it over:
//this is the timer
networkWriteTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:#selector(sendActivity:) userInfo:nil repeats:YES];
-(void)sendActivityInBackground:(id)sender
{
[[AppConfig getInstance].activeRemoteRoom.connection sendNetworkPacket:[circularArray objectAtIndex:arrayIndex%arrayCapacity]];
}
-(void)sendActivity:(NSTimer*)timer
{
// Send it out
[self performSelectorInBackground:#selector(sendActivityInBackground:) withObject:nil];
}
I'm not satisfied with the performance of this method. Time profiling has revealed that there's overhead associated with performing background selectors, and the performance can get quite choppy.
I'm thinking of additional ways to improve performance:
Improve the performance of the current timer based code
Try grand central dispatch
Implement NSOperationsQueue with a single operation.
Use a dedicated thread that wakes up, checks for update and sends it over if needed
Ideally, I would send data at even faster intervals (10ms or even for each activity update). This poses the question: What is the fastest way to implement a sequence of background send requests, where order matters? I want to make sure that one packet gets send before the next one is being sent.
Try a recurring dispatch timer (that is part of gcd):
self.synchronizerQueue = dispatch_queue_create("synchronizer queue", DISPATCH_QUEUE_SERIAL);
self.synchronizeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.synchronizerQueue);
dispatch_source_set_timer(self.synchronizeTimer, DISPATCH_TIME_NOW, NSEC_PER_MSEC * 10, NSEC_PER_MSEC * 1);
dispatch_source_set_event_handler(self.synchronizeTimer, ^
{
// do your processing here
});
dispatch_resume(self.synchronizeTimer);

Threading issue in iOS app

I am somewhat new to iOS development and am having an issue with threading. I am calling a web service that returns json data and the code to perform this action works as expected. For testing, i would like to be able to click a button, retrieve the data and populate a textview control with formatted results. Here is my code excerpted from a button click event handler:
dispatch_queue_t que = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(que, ^{
thisRiverGauge = [[RiverGauge alloc] initWithStationInfo:gauge forHistoryInHours:5 inThisFormat:#"json"];
[txtResults setText:rval];
});
When trying to update the textview (txtResults) from within the thread, I get a runtime error. When I place the update to the textview outside of the thread, obviously it won't update because the thread takes longer to complete than the execution of the event handler. What might be a solution to this?
Thx!
You should perform GUI related tasks on the main thread, add the main queue/thread block around the code where you are updating the value for textview.
ispatch_queue_t que = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(que, ^{
thisRiverGauge = [[RiverGauge alloc] initWithStationInfo:gauge forHistoryInHours:5 inThisFormat:#"json"];
dispatch_async(dispatch_get_main_queue(), ^{
[txtResults setText:rval];
});
});

preparing view elements on a background thread

OK, so I know you're not supposed to directly interact with view elements from any thread other than the main thread.
But can you do stuff in a background thread that will be used by a view?
In particular, I have a pretty substantial algorithm that ends up spitting out a string. If I want that string to become the text of a UITextView, do I need to run this whole algorithm on the main thread? Or can it be done in the background ?
You can certainly run it in the background, just like a graphical application might render images in the background. Once you have the string ready, GCD is your friend:
- (void)backgroundStringGenerator
{
NSString *expensiveString = ... // do string generation algorithm
dispatch_async(dispatch_get_main_queue(), ^{
theLabel.text = expensiveString;
});
}

Asynchronous function execution?

in my iOS app I do the following.
viewDidAppear(){
// Load a spinner in a view on the top
[DSBezelActivityView newActivityViewForView:self.view];
// Execute code that require 3 seconds
...
// Stop the spinner
[DSBezelActivityView removeViewAnimated:YES];
}
The problem is that the spinner doesn't appear, because the the cpu is working hard (something similar). It's like that the code betweek the start and stop has precedence on the rendering of the view.
I would love to find a way to show effectively the start of the spinner, without using a timer to delay the code execution.
Thanks
If you have a method like
-(void) showSpinner:(UIView*)view {
dispatch_async(dispatch_get_main_queue(), ^{
[DSBezelActivityView newActivityViewForView:view];
});
}
there are several ways to call it from a different thread. Choose one from the following:
[NSThread detachNewThreadSelector:#selector(showSpinner:) toTarget:self withObject:self.view];
// or
[self performSelectorInBackground:#selector(showSpinner:) withObject:self.view];
// or
NSInvocationOperation *invOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(showSpinner:) object:self.view];
NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];
[opQueue addOperation:invOperation];
// or
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self showSpinner:self.view];
});
Alt + click for details.
Move code between start and stop activity indicator into separate thread because it's blocking main thread. That's why activity indicator is not showing.
Edit: Example
I agree with the 1st answer with a couple of modifications. I just went through this exact same problem. The problem is that anything graphical is going to automatically move to the background updating when you have code that takes time to get through. Throwing the spinner to the background is what it essantially doing anyway. What you want is (sadly) for you main code to run in the background and the spinner to run in the foreground. I know this sounds bad, but in some cases allowing your code to run a bit slower to give indication that the app is doing something useful is beneficial to the user.
In order to get the spinner to work:
1) Take all the code that takes the 3 seconds to run, and put that into a function that is a void function
2) Instantiate your spinner but store it to a variable that is accessible outside your viewDidAppear routine.
3) Startup a new NSTimer with that runs continuously with an increment of about every quarter second or so. I will define what goes into the routine that gets called every cycle later.
4) Call the routine you created in step 1 using the performSelectorInBackground capability. This essentially is now going to run your startup (3 seconds worth) in the background which is really the only way to allow the animated spinner to show up and truly animate.
5) In the routine you created in step 1, add a line of code right at the top that updates a (global to the object) boolean to true, stating that we are in the middle of our main 3 second routine.
6) At the end of the routine defined in step 1 add a line of code setting the same global defined in step 5 to false indicating that our 3 second routine is completed.
7) In the timer routine, we now want to do something that looks like the following:
// If busy that start the spinner
if(YES == busy){
[spinner startAnimating];
}else{
[spinner stopAnimating];
// Here we can also stop and deallocate the timer
}
If you need more aid on this subject, I can indeed provide exact code. Take a look at the example app that I have developed for the Pepperdine News Group. When you press a button, the spinner comes up on the top right of the screen.
http://itunes.apple.com/us/app/pepperdine-graphic-for-iphone/id516343215?mt=8

When are a methods GUI operations actually carried out?

I am working on a web-services data processing app and I am trying to make the app run as quickly as possible. When a certain 3 finger pan gesture is performed, I call a method that sends updated information off to the server to get a new batch of images to update the existing ones with.
So lets say there are 15 images in an array, I filter through them with a 2 finger gesture, and then if I want to change something about them, I can do the 3 finger gesture, and I get that same set back, just tweaked a bit (contrast/brightness, etc.).
Is what I want though is to be able to update the imageView that is displaying the images after the first image has been retrieved, so as to give the user a feel for what the rest in the series are going to look like. But no matter what I try, and no matter how many different threads I try and implement, I can't get the imageView to update before the entire download is complete. Once the batch download is done (which is handled on a separate thread) the imageView updates with the new images and everything is great.
The first step in the process is this:
if(UIGestureRecognizerStateEnded == [recognize state]){
[self preDownload:windowCounter Level:levelCounter ForPane:tagNumber];// Where this method is what gets the first image, and tries to set it to the imageView
[self downloadAllImagesWithWL:windowCounter Level:levelCounter ForPane:tagNumber]; //And this method goes and gets all the rest of the images
}
This is my preDownload method:
-(void)preDownload:(int)window Level:(int)level ForPane:(int) pane{
int guidIndex = [[globalGuids objectAtIndex:pane] intValue];
UIImage *img = [DATA_CONNECTION getImageWithSeriesGUID:[guids objectAtIndex:guidIndex] ImageID:counter Window:window Level:level];
if(pane==0){
NSLog(#"0");
[imageView3 setImage:img];
}else if(pane==1){
NSLog(#"1");
[imageView31 setImage:img];
}else if(pane==2){
NSLog(#"2");
[imageView32 setImage:img];
}else if(pane==3){
NSLog(#"3");
[imageView33 setImage:img];
}
}
So by separating this out into two different methods (there are no threads being implemented at this point, these methods are being called before all that) I was thinking that after the preDownload method completed, that the imageView would update, and then control would continue on down into the downloadAllImagesWithWL method, but that doesn't appear to be the case.
Am I missing something simple here? What can I do to update my GUI elements before that second method is through running?
You are right. However the viewn won't refresh until your code reaches runloop. You can do 2 things:
Make your downloadAllImagesWithWL method async, so it will return after you called it, your main thread reaches runloop, gui updates, and the download method will tell your logic through a callback when its done.
OR
A simplier hackier (and bad) solution would be to run runloop for some time before you call your download method. Something like this: [[NSRunloop currentRunLoop] runUnitlDate: [Date dateWithTimeIntervalSinceNow: 0.1]]; It will run runloop for 0.1 second.
When the image is set, the image view will mark itself as needing display. The actual display won't occur until the beginning of the next run loop. In OS X, you can use -display to draw the view immediately, but I don't think Apple created a public method to do this on iOS. However, if the next method simply creates the background thread, then it will return quickly and the display update will probably occur before the thread finishes.