Warn on calls to UIKit from background threads
iOS's UIKit is not thread-safe, let us call this fact well known. I know the rule, I'm careful, but I still get bitten - and every now and then the resulting crash is far enough removed from the offending background call into UIKit to make tracking down the problem a less than joyeus experience.
This problem seems like it could be easy to solve - have UIKit classes/methods warn when they are invoked from a background thread, at least as a debug feature. As far as I'm aware, iOS does not provide any such feature. Of course one could achieve the same effect manually by having some form of assertions precede such calls, but this solution is not the most elegant and in addition suffers from the same weakness as the original problem, namely that programmers are prone to forgetfulness.
Does anyone have a more elegant solution? How do you deal with this problem in your projects?
(Note: this question is related, but not quite as explicit. One is left wondering)
UPDATE : Andrew's answer is the solution I was looking for at the time, however note that at least as of Xcode 9 this is now provided by xcode/ios. For instance, adding this code:
DispatchQueue.global().async {
print(self.view.frame)
}
To a UIView's viewDidLoad method produces a runtime warning inline in Xcode UIView.frame must be used from the main thread only and a message printed to the console: Main Thread Checker: UI API called on a background thread: -[UIView frame]
This code (just add to project and compile this file without ARC) causes assertions on UIKit access outside of the main thread: https://gist.github.com/steipete/5664345
I've just used it to pickup numerous UIKit/main thread issues in some code I've just picked up.
I try not to introduce multi threading unless I have tried a single threaded approach first but it depends on the problem you are trying to solve.
Even when multithreading is the only option I usually avoid long running background operations or operations that that perform several unrelated tasks.
Just my opinion.
Edit
An example of doing work on the main thread whilst displaying a loading spinner:
MBProgressHUD *hud = [MBProgressHUD customProgressHUDInView:view dim:dim];
[hud show:NO];
//Queue it so the ui has time to show the loading screen before the op starts
NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:block];
NSBlockOperation *finOp = [NSBlockOperation blockOperationWithBlock:^{
[MBProgressHUD hideAllHUDsForView:view animated:NO];
}];
[finOp addDependency:blockOp];
[[NSOperationQueue mainQueue] addOperations:@[blockOp, finOp] waitUntilFinished:NO];
Personally anytime I open the box to a multithreaded approach I start wrapping ALL interface based calls in performSelectorOnMainThread:
so that there is never an issue. If we've already made it to the main there this call shouldn't cause any significant slow down, but if its called from a background thread I can rest easy knowing its safe.
下一篇: 警告从后台线程调用UIKit