creating UI elements on NSOperationQueue non-main queues causes strange behavior - ios

I had some code that created and added UIView subviews via a queue created by [[NSOperationQueue alloc] init], and it led to strangely laggy behavior. Subviews were only added after abnormally long delays.
But then I switched to using [NSOperationQueue mainQueue] for these parts, and responsiveness turned normal.
I would just like an explanation for the laggy behavior I saw using the first approach.

From Apple doc
Threads and Your User Interface
If your application has a graphical user interface, it is recommended that you receive
user-related events and initiate interface updatesfrom your
application’s main thread. This approach helps avoid synchronization
issues associated with handling user events and drawing window
content. Some frameworks,such as Cocoa, generally require this
behavior, but even for those that do not, keeping this behavior on the
main thread hasthe advantage of simplifying the logic for managing
your user interface. There are a few notable exceptions where it is
advantageous to perform graphical operations from other threads. For
example, the QuickTime API includes a number of operationsthat can be
performed from secondary threads, including opening movie files,
rendering movie files, compressing movie files, and importing and
exporting images. Similarly, in Carbon and Cocoa you can use secondary
threads to create and process images and perform other image-related
calculations. Using secondary threads for these operations can greatly
increase performance. If you are not sure about a particular graphical
operation though, plan on doing it from your main thread
In addition, UI classes are not thread-safe according to the thread programming guide.
So, avoid to update the UI from a thread different from the main thread.
If you run an NSOperation (within a queue) you could update your UI (for example after having download some data required for your app lifecycle) performing a method in the main thread like the following:
-(void)main {
// e.g the delegate could be the controller that has the view that you want to update
if (delegate) {
NSURL *url = [delegate urlForDownloadOperation:self];
if ( nil == url ) return;
self.downloadedImage = [[NSImage alloc] initWithContentsOfURL:url];
// e.g. rather than invoking processImage directly on the delegate, ensure that the method draw the image on the main thread
[delegate performSelectorOnMainThread:#selector(processImage:)
withObject:self waitUntilDone:YES];
}
}
Or you could send a notification to the component that need to update the UI like:
- (void)main {
NSURL *url = [delegate urlForDownloadOperation:self];
if ( nil == url ) return;
self.downloadedImage = [[NSImage alloc] initWithContentsOfURL:url];
// e.g. send a notificatio to inform some components that it is ready to update the UI with some content
[[NSNotificationCenter defaultCenter] postNotificationName:#"importData" object:self];
}
The component that needs to update the UI will register for that notification like
- (void)processImage:(NSNotification*)notification
{
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(processImage:) withObject:notification waitUntilDone:YES];
return;
}
// update the UI here, you are running on the main thread
}

Related

iOS - When To Call UI Changing Functions In The Main Thread

Every time I make an API call to my server to get data, I already know that I have to use the following block to execute UI changing commands because my API call executes in the background thread:
dispatch_async(dispatch_get_main_queue(), ^{
//do UI stuff
});
However, what if I have a function that does UI changing stuff outside of the API call block? For example:
-(void)doALotOfUIChanging
{
//do a lot of UI changing
}
In my API call block, do I need to call that UI changing function in the main thread like so?:
[apiObject getDataFromObject:my.Object successCallback:^(Array *data)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self doALotOfUIChanging];
});
}
errorCallback:^(NSString *error)
{
NSLog(#"%#", error);
}];
Or do I not have to call it in the main thread since the function is already outside of the API call block like so?:
[apiObject getDataFromObject:my.Object successCallback:^(Array *data)
{
[self doALotOfUIChanging];
}
errorCallback:^(NSString *error)
{
NSLog(#"%#", error);
}];
I also have functions that perform segues to other view controllers, so I'm also wondering if I should call them in the main thread as well. I'm doing some code clean up and I don't want to have to constantly rewrite the dispatch_async function in situations that I might not have to, so any help or advice would be greatly appreciated. Thanks.
Short answer: Yes you should update your UI on main thread.
Threads and Your User Interface
If your application has a graphical user interface, it is recommended
that you receive user-related events and initiate interface updates
from your application’s main thread. This approach helps avoid
synchronization issues associated with handling user events and
drawing window content. Some frameworks, such as Cocoa, generally
require this behavior, but even for those that do not, keeping this
behavior on the main thread has the advantage of simplifying the logic
for managing your user interface.
There are a few notable exceptions where it is advantageous to perform
graphical operations from other threads. For example, you can use
secondary threads to create and process images and perform other
image-related calculations. Using secondary threads for these
operations can greatly increase performance. If you are not sure about
a particular graphical operation though, plan on doing it from your
main thread.
Reference:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/AboutThreads/AboutThreads.html
First of all you should never use self inside a block . You can use __weak yourClassName *weakSelf = self instead.
Regarding your problem, all UI changes should be done on main thread. So you need to do this :
[apiObject getDataFromObject:my.Object successCallback:^(Array *data)
{
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf doALotOfUIChanging];
});
}
Hope it helps. :)

How to load page content within a new UIWebView before exiting current method

I have an application with implemented local server which handles request from web front-end.
UIWebView presents some GUI, user do some interaction, I handle his requests and send responses back to webview.
Sometimes I'm receiving some request which require to open second webview (e.g for facebook login) and wait in current method for results to return from that second webview.
When I'm running such case on iDevice with dual core processor it works as expected.
But when I'm running it on single core iPhone 4, webview processing is blocked until i leave the current method (white page with waiting indcator).
I solved this by putting sleep for current thread so the main thread will have time to process events in his run loop (if I properly understand this)
- (void)requestProcessingMethod { // <-- on background thread
someCalculations ...
dispatch_sync(dispatch_get_main_queue(), ^{
[self displayFacebookLoginWebView];
});
while(!facebookReturnCondition){
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
[NSThread sleepForTimeInterval:0.5]; // <-- on single core without this
// facebook webview will not load the login page
}
return response; //
}
I'm not happy with that. Setting thread for sleep looks like very poor solution.
Is there any way to add working UIWebView from background thread without exiting current method (background thread)?
Or is it possible to manually switch/force run loop execution?
Now I am not sure if this will work, can't test at the moment. But:
You could try having the operations as separate operations in a queue, using Operation Dependencies.
in your case when you use this method:
-(void)methodThatCallsRequestProcessingMethod {
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSInvocationOperation* theMainThreadOp = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(displayFacebookLoginWebView) object:nil];
NSInvocationOperation* theOp = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(requestProcessingMethod) object:nil];
[theOp addDependency: theMainThreadOp];
[[NSOperationQueue mainQueue] addOperation: theMainThreadOp];
[queue addOperation:theOp];
}
From reading the Concurrency Guide, it appears you can have operation dependencies over different queues. Which means that you can ensure the UI runs in the main thread, web later running in an async thread, keeping the Main Thread clear and responsive on all devices.
Apple Concurrency Guide

Lengthy core-data delete run in background/foreground mode

I have a VERY lengthy core-data delete task of 92k some-odd records which takes a really long time to run. Initially it was locking up the UI thread so I attempted to stick the call on a background thread. What I noticed, in the simulator, however, is that when I background the app this task stops running.
I'm wondering if there is a quick tweak I can make the the code so my delete will continue running once the application has been backgrounded for the 10 minutes or however long they are allowed to run.
When I start up my class I make a background queue:
backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, nil);
Then when its time to delete my objects
- (void)queueForDelete:(FlightRecording *)flight {
NSError *error = nil;
flight.deleteFlagValue = 1;
[managedObjectContext save:&error];
// Remove flights form Internal table dataStructure
[flightList removeObjectAtIndex:[tmpIndexPath row]];
[[self tableView] deleteRowsAtIndexPaths:#[tmpIndexPath] withRowAnimation:UITableViewRowAnimationFade];
//Queue flight for deletion in the background
dispatch_async(backgroundQueue, ^{
NSLog(#"Adding %# to deletion Queue", flight.getFileName);
[self deleteFlight:flight];
});
[[self tableView] reloadData];
}
This code did make the UI more responsive but it still takes about 5 minutes - 10 minutes (in the simulator) to delete my objects. I would like the user to be able to background the app and have the delete process still running because regardless of background/foreground it does have an impact on the responsiveness of the application.
Do this task within an NSManagedObjectContext -performBlock: call. Do it on a child context with private queue concurrency type. That will give you asynchronous processing.
Don't save your context on each call.
Don't update your tableview (reloadData, delteRowsAtIndexPaths) on each call. This is a Model task. Don't update the UI.
Your use of tmpIndexPath is a code smell. That's another sign of mixing Model tasks with View tasks. Deleting a bunch of objects is purely a Model task. Write the code such that no UI or Controller is assumed to exist.

Using multithread to save data on iOS

I am developing an iPhone app which keeps some data. I am using archiving method from NSKeyedArchiver class to save the data to disk. I would like to periodically save the data to disk. The problem is, when the data grows bigger, it takes more time and it actually interrupts the user's current actions.
Hence, I want to use multithreading to solve this problem. From my understanding of multithreading, when I want to save the data to disk, I should create a new thread, run the saving task on the new thread, then terminate the thread. I should also make the thread so that it won't immediately terminate when the app terminates, to finish saving data. This way, the user can continue to interact with the interface.
That being said, I am not familiar with the actual code that does these work...what would the above look like in code?
A couple of thoughts.
You want to use a serial dispatch queue or operation queue.
Note, we probably want it to write to persistent storage serially (if you're saving it to the same filename, for example), i.e. not permit another save to be initiated until the prior save is finished. I suspect that's exceedingly unlikely that your infrequent saves could ever trigger a save while the prior one is still in progress, but as a general principle you should not use concurrent queues unless you write code that supports concurrent operation (which we're not doing here). This means that you do not use the GCD global queues.
For example, to create serial dispatch queue using Grand Central Dispatch (GCD) would be:
#property (nonatomic, strong) dispatch_queue_t queue;
Then instantiate this (e.g. in viewDidLoad):
self.queue = dispatch_queue_create("com.domain.app.savequeue", 0);
Then use this queue
dispatch_async(self.queue, ^{
// do your saving here
});
For a review of concurrency technologies, see the Concurrency Programming Guide. Both dispatch queues (GCD) and operation queues are solid choices.
You might want to be careful about synchronization issues. What if your app proceeds to start changing the data while the save is in progress? There are a bunch of options here, but the easiest is to copy the data to some temporary object(s) in the main queue before you dispatch the save task to the background queue:
// copy the model data to some temporary object(s)
dispatch_async(self.queue, ^{
// save the temporary object(s) here
});
Or, instead of creating a copy of the model, you can alternatively (and this is a little more complicated if you're not familiar with GCD) use a variation of the "reader-writer" pattern that Apple discusses in WWDC 2012 video Asynchronous Design Patterns with Blocks, GCD, and XPC. Bottom line, you can queue to not only perform asynchronous write to persistent storage, but also to synchronize your updates to your model using a "barrier" (see Using Barriers in the GCD reference):
self.queue = dispatch_queue_create("com.domain.app.modelupdates", DISPATCH_QUEUE_CONCURRENT);
Then, when you want to save to disk, you can do
dispatch_async(self.queue, ^{
// save model to persistent storage
});
But, whenever you want to update your model, you should use barrier so that the updating of the model will not happen concurrently with any read/save tasks:
dispatch_barrier_async(self.queue, ^{
// update model here
});
And, whenever you read from your model, you would:
dispatch_sync(self.queue, ^{
// read from model here
});
Theoretically, if you're worried about the possibility that you could conceivably do your save operations so frequently that one save could still be in progress when you initiate the next one, you might actually employ two queues, one serial queue for the saving operation (point 1, above), and the concurrent queue outlined here for the synchronization process.
Finally, Putz1103 is correct, that if it's possible that the app can be terminated while a save is in progress, you might want to add the code to allow the write to persistent storage to complete:
dispatch_async(self.queue, ^{
UIBackgroundTaskIdentifier __block taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(void) {
// handle timeout gracefully if you can
[[UIApplication sharedApplication] endBackgroundTask:taskId];
taskId = UIBackgroundTaskInvalid;
}];
// save model to persistent storage
// when done, indicate that the task has ended
if (taskId != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:taskId];
taskId = UIBackgroundTaskInvalid;
}
});
Adding multi-threading to an application where data is shared between multiple threads (in this case, the data being created by the user and the data you are saving) is a difficult task to manage.
Instead of creating a thread or trying to save off all the data at once, put data to be saved into an internal "this must be saved off" list, and then work it off one N elements at a time periodically in your main thread.
If you get to the point where the user is leaving the screen or the app, then save off all the work that is left in the queue to the database immediately.
You can create a simple timed event (a few times per second) to do the work, which is a very simple approach.
You can explicit control over how many items you save per update.
You should never have concurrency issues.
You should never have to worry about thread start/stop/termination
issues or mutexes.
To create it:
-(void)viewDidAppear:(BOOL)animated
{
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(saveData) userInfo:nil repeats:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(saveAllRemainingItems) name:#"APP EXITING KEY" object:nil]
}
Your update function:
-(void)saveData
{
if([itemsToSave count] > 0)
{
....save off N items, remove from the list
}
}
-(void)saveAllRemainingItems
{
while([itemsToSave count] > 0)
{
...save first item.
[itemsToSave removeObjectAtIndex:0];
}
}
When you leave:
-(void)viewWillDisappear:(BOOL)animated
{
[self.timer invalidate];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self saveAllRemainingData];
}
To make sure you handle the "app is closing" situation, in your app delegate:
- (void)applicationWillTerminate:(UIApplication *)application
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"APP EXITING KEY" object:nil];
...OTHER CLEANUP ACTIVITIES
}
You can achive mulithreading in IOS with different ways, like NSThread, Operation Queues and GCD.
GCD is the best approch now a days, it uses block. You can execute a code in different thread like this. You can use this in any method.
void performArchiveData{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Now you are in different thread. You can add your code in here.
});
}

Using NSUrlConnection inside either NSThread or NSOperation

I am developing a static library that needs to do some stuff in the background, without interacting with the main thread. To give you an idea, think of just logging some user events. The library must keep doing this stuff until the user exits the app or sends it to the background (pushes the home button) - in other words it needs to keep doing stuff inside a loop.
The only interaction between the main app thread and the spawned thread is that occasionally the main app thread will put some stuff (an event object) into a queue that the spawned thread can read/consume. Other than that, the spawned thread just keeps going until the app exists or backgrounds.
Part of what the spawned thread needs to do (though not all of it) involves sending data to an HTTP server. I would have thought that it would be easy to subclass NSThread, override its main method, and just make a synchronous call to NSUrlConnection with some sort of timeout on that connection so the thread doesn't hang forever. For example, in Java/Android, we just subclass Thread, override the start() method and call a synchronous HTTP GET method (say from Apache's HttpClient class). This is very easy and works fine. But from what I have seen here and elsewhere, apparently on iOS it is much more complicated than this and I'm more than a bit confused as to what the best approach is that actually works.
So should I subclass NSThread and somehow use NSUrlConnection? It seems the asynchronous NSUrlConnection does not work inside NSThread because delegate methods don't get called but what about the synchronous method? Do I somehow need to use and configure the RunLoop and set up an autorelease pool? Or should I use an NSOperation? It seems to me that what I am trying to do is pretty common - does anyone have a working example of how to do this properly?
As I understand it, to use NSURLConnection asynchronously you need a runloop. Even if you use an NSOperation you still need a runloop.
All the examples I have seen use the Main Thread to start NSURLConnection which has a runloop. The examples using NSOperation are set up so the operation is Concurrent which tells NSOperationQueue not to provide it's own thread, they then make sure that NSURLConnection is started on the main thread, for example via a call to performSelectorOnMainThread:
Here is an example:
Pulse Engineering Blog: Concurrent Downloads using NSOperationQueues
You can also search the Apple documentation for QRunLoopOperation in the LinkedImageFetcher sample which is an example class showing some ins and outs of this kind of thing.
(Although I'm not sure I actually saw any code that example showing how to run your own runloop, again this example relies on the main thread.)
I've used the grand central dispatch (GCD) methods to achieve this. Here is an example that worked for me in a simple test app (I'm not sure if it applies in a static library, but may be worth a look). I'm using ARC.
In the example, I am kicking off some background work from my viewDidLoad method, but you can kick it off from anywhere. The key is that "dispatch_async(dispatch_get_global_queue…" runs the block in a background thread. See this answer for a good explanation of that method: https://stackoverflow.com/a/12693409/215821
Here is my viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL),
^(void) {
[self doStuffInBackground];
});
}
The doStuffInBackground method is running in the background at this point, so you can just use NSURLConnection synchronously. In my example here, the method loops making network calls until presumably some other code sets backgroundStuffShouldRun = false. A network call is made with a 10 second timeout. After the call, I'm updating a UI label just to show progress. Note that the UI update is performed with "dispatch_async(dispatch_get_main_queue()…". This runs the UI update on the UI thread, as required.
One potential issue with this background work: there isn't a way to cancel the http request itself. But, with a 10 second timeout, you'd be waiting a max of 10 seconds for the thread to abort itself after an outsider (likely some event in your UI) sets backgroundStuffShouldRun = false.
- (void)doStuffInBackground
{
while (backgroundStuffShouldRun) {
// prepare for network call...
NSURL* url = [[NSURL alloc] initWithString:#"http://maps.google.com/maps/geo"];
// set a 10 second timeout on the request
NSURLRequest* request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLCacheStorageAllowed timeoutInterval:10];
NSError* error = nil;
NSURLResponse *response = nil;
// make the request
NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// were we asked to stop the background processing?
if (!backgroundStuffShouldRun) {
return;
}
// process response...
NSString* status = #"Success";
if (error) {
if (error.code == NSURLErrorTimedOut) {
// handle timeout...
status = #"Timed out";
}
else {
// handle other errors...
status = #"Other error";
}
}
else {
// success, handle the response body
NSString *dataAsString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#", dataAsString);
}
// update the UI with our status
dispatch_async(dispatch_get_main_queue(), ^{
[statusLabel setText:[NSString stringWithFormat:#"completed network call %d, status = %#", callCount, status]];
});
callCount++;
sleep(1); // 1 second breather. not necessary, but good idea for testing
}
}

Resources