preparing view elements on a background thread - iphone

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;
});
}

Related

When to use threading for UI updates in SWIFT

I am confused about when to put code that updates the UI on the main queue:
dispatch_async( dispatch_get_main_queue() )
{
// Do UI update here
}
Online sources such as https://www.raywenderlich.com/79149/grand-central-dispatch-tutorial-swift-part-1 suggest to use that approach. However, many swift/iOS tutorials do not apply that coding pattern, especially when it comes to small UI updates such as button.hidden = false or button.backgroundColor = UIColor.blueColor().
All UI elements should be update in the main thread of the application. It is an golden rule if you would like to see proper transitions and smoothly updated ui.
If you are working on the main thread there is no need to use :
dispatch_async( dispatch_get_main_queue() )
{
// Do UI update here
}
because you are in the main thread of the application.
You need to use code block that you present in the situation when you are working on another thread or in another operation and you need to update UI in these threads.
So imagine situation you need to do some calculation in the background thread and update UI in this thread.
Code solution example:
//Async operation in with we would like to do some calculation and do not block main thread of the application.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let result = doSomeCalculation()
//After we receive result of the calculation we need to update UI element `UIlable` so we call main thread for that.
dispatch_async(dispatch_get_main_queue()) {
label.stringValue = result
}
}

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

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.");
});
}

GCD pattern for shared resource access + UI update?

folks! I'm implementing a shared cache in my app. The idea is to get the cached data from the web in the background and then update the cache and the UI with the newly retrieved data. The trick is of course to ensure thread-safety, since the main thread will be continuously using the cache. I don't want to modify the cache in any fashion while someone else might be using it.
It's my understanding that using #synchronized to lock access to a shared resource is not the most elegant approach in ObjectiveC due to it trapping to the kernel and thus being rather sluggish. I keep reading that using GCD instead is a great alternative (let's ignore its cousin NSOperation for now), and I'd like to figure out what a good pattern for my situation would be. Here's some sample code:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// download the data in a background thread
dispatch_async(queue, ^{
CacheData *data = [Downloader getLatestData];
// use the downloaded data in the main thread
dispatch_sync(dispatch_get_main_queue(), ^{
[AppCache updateCache:data];
[[NSNotificationCenter defaultCenter] postNotificationName:#"CacheUpdated" object:nil];
});
});
Would this actually do what I think it does, and if so, is this the cleanest approach as of today of handling this kind of situation? There's a blog post that's quite close to what I'm talking about, but I wanted to double-check with you as well.
I'm thinking that as long as I only ever access shared the shared resource on the same thread/queue (main in my case) and only ever update UI on main, then I will effectively achieve thread-safety. Is that correct?
Thanks!
Yes.
Other considerations aside, instead of shunting read/write work onto the main thread consider using a private dispatch queue.
dispatch_queue_t readwritequeue;
readwritequeue = dispatch_queue_create("com.myApp.cacheAccessQueue", NULL);
Then update your AppCache class:
- (void)updateCache:(id)data {
dispatch_sync(readwritequeue, ^{ ... code to set data ... });
}
- (id)fetchData:... {
__block id data = nil;
dispatch_sync(readwritequeue, ^{ data = ... code to fetch data ...});
return data;
}
Then update your original code:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// download the data in a background thread
dispatch_async(queue, ^{
CacheData *data = [Downloader getLatestData];
**[AppCache updateCache:data];**
// use the downloaded data in the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"CacheUpdated" object:nil];
});
});
If you ask 100 developers here was is the most elegant way to do this, you will get at least 100 different answers (maybe more!)
What I do, and what is working well for me, is to have a singleton class doing my image management. I use Core Data, and save thumbnails directly in the store, but use the file system and a URL to it in Core Data for "large" files. Core Data is setup to use the new block based interface so it can do all its work on a private thread managed by itself.
Possible image URLS get registered with a tag on the main thread. Other classes can ask for the image for that tag. If the image is not there, nil is returned, but this class sets a fetchingFlag, uses a concurrent NSOperation coupled to a NSURLConnection to fetch the image, when it gets it it messages the singleton on its thread with the received image data, and the method getting that message uses '[moc performBlock:...]' (no wait) to process it.
When images are finally added to the repository, the moc dispatches a notification on the main queue with the received image tag. Classes that wanted the image can listen for this, and when they get it (on the main thread) they can then ask the moc for the image again, which is obviously there.

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.