Thread-Safe UIkit Methods

UIKit drawing is thread safe: how do you get a graphics context?

My belief, based on some experience trying it and based on various documents, is that the docs for UIGraphicsPushContext() are incorrect.

I believe UIGraphicsPushContext() is in fact thread safe. The particular indication that this is true is QA1637, which says "Beginning with iOS 4.0, drawing to a graphics context in UIKit is thread-safe. This includes accessing and manipulating the current graphics stack, drawing images and strings, and usage of color and font objects from secondary threads." (emphasis mine)

I acknowledge that it is always a dicey proposition to assume thread safety in contradiction to the docs. But I believe this is a documentation error. I have opened rdar://11161530 to track it. Please dupe.

Are uikit methods guaranteed to be run on main thread?

You're confused.

A given call chain is either run on the main thread or on a background thread. Most of the code you deal with is invoked through system events, and is guaranteed to be run on the main thread. There are only a limited number of cases where your code will be invoked from a background thread. Only in those cases do you need to wrap UIKit calls in code that redirects the call to the main thread.

The most common reason for your code to be running from a background thread is because you invoked it that way - often using Grand Central Dispatch.

You might invoke some code like this:

DispatchQueue.global(qos: .userInitiated).async { [weak self] in
//This code will be on a background thread
//Do time-consuming work here

//This calls back to the main thread.
DispatchQueue.main.async { [weak self] in
//Put your code to display your results in the UI here.
}
}

View controller functions, button actions, etc are always called on the main thread. If the system calls some method on a background thread, it will be well documented.

Some functions that take completion handlers invoke those completion handlers on a background thread. The NSURLSession (URLSession) class, for example, invokes its completion handlers and delegate methods on a delegate queue that defaults to a background thread. So when you pass a completion handler to an instance of URLSession, you need to make sure your UIKit code is wrapped in a call to dispatch_async() (DispatchQueue.main.async() in Swift 3) or a similar method of passing code to the main thread to execute.

Thread safety of UIImage

It is true that apple recommends using elements from the UIKIt on the main thread:

Note: For the most part, UIKit classes should be used only from an
application’s main thread. This is particularly true for classes
derived from UIResponder or that involve manipulating your
application’s user interface in any way.

Since UIImage isn't derived from UIResponder, and you do not actually display it on the interface/screen. Then doing operations with UIImages on another thread should be safe.

This is however based on my experience, I haven't seen any official documentation about it.

Do UIKit objects need to dealloc on the main thread?

Yes, UIKit objects need to dealloc on the main thread. It is not safe to dealloc a UIView object in a background thread. See here for more details.

iOS: is calling methods of UIApplication in thread other than the main thread safe?

Most UIKit classes are not thread safe. This has been discussed at length at WWDC, etc, but it is not very well documented. The best I could find is TN2109 which repeatedly discusses how calling UIKit from a secondary thread is not permitted.

Note that there are many documented exceptions to this rule. For example, the beginBackgroundTaskWithExpirationHandler and related methods on UIApplication are clearly labeled as safe to call from non-main threads. (This also implies that the other methods of UIApplication are not safe to call from other threads, since these are specifically called out.)

There are other exceptions to this rule as well, mostly involving drawing (UIImage, and UIColor instances are thread-safe, as of iOS 4, at least).

Issues related to calling UIKit methods from non-main thread

You MUST call methods that update the user interface in any way from the main thread, as per the UIKit documentation:

For the most part, use UIKit classes only from your app’s main thread. This is particularly true for classes derived from UIResponder or that involve manipulating your app’s user interface in any way.

I suggest you try to limit the number of callbacks you make to the main thread, so therefore you want to batch as much user interface updates together as you can.

Then all you have to do, as you correctly say, is to use a dispatch_async to callback to your main thread whenever you need to update the UI, from within your background processing.

Because it's asynchronous, it won't interrupt your background processing, and should have a minimal interruption on the main thread itself as updating values on most UIKit components is fairly cheap, they'll just update their value and trigger their setNeedsDisplay so that they'll get re-drawn at the next run loop.


From your code, it looks like your issue is that you're calling this from the background thread:

if ([_usernameField.text length] < 4) {
[KVNProgress showErrorWithStatus:@"Username too short!"];
_passwordField.text = @"";
return;
}

This is 100% UI updating code, and should therefore take place on the main thread.

Although, I have no idea about the thread safety of KVNProgress, I assume it should also be called on the main thread as it's presenting an error to the user.

Your code therefore should look something like this (assuming it's taking place on the main thread to begin with):

[KVNProgress show];

//some error handling like:
if ([_usernameField.text length] < 4) {
[KVNProgress showErrorWithStatus:@"Username too short!"];
_passwordField.text = @"";
return;
}

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

//Then I call login web service synchronously here:
result = [ServerRequests login];

dispatch_async(dispatch_get_main_queue(), ^{
if(!result) {
[KVNProgress showErrorWithStatus:@"problem!" completion:NULL];
_passwordField.text = @"";
} else if([result.successful boolValue]) {
[KVNProgress showSuccessWithStatus:result.message];
}
});
});

UIStringDrawing methods don't seem to be thread safe in iOS 6

While trying to find a work around I noticed that iOS 6 introduces much more widespread integration of NSAttributedString and Core Text so I tried swapping all UIStringDrawing methods with the equivalent NSStringDrawing methods using NSAttributedString in place of NSString and it seems the crashes have stopped.

For example, I'm now using:

NSAttributedString *attribStr = [[NSAttributedString alloc] initWithString:@"My String"];
CGSize size = [attribStr size];

instead of:

NSString *str = @"My String";
CGSize size = [str sizeWithFont:font];


Related Topics



Leave a reply



Submit