Why is that constantly animating UIActivityIndicator won't block main thread? - ios

We all show activity indicator while some lengthy operation is happening in background. Though the activity indicator shows a constantly rotating wheel it won't burden the main thread, because other UIComponents in the same screen still react to the touches.
What I think I know:
I know all touch events are handled by main thread, and main Queue is being used to queue the events. Considering main queue is Serialized queue and only one task at a time can run at any given point in time, alley touch events should get queued up in main queue, while my main thread is busy in refreshing the screen/calling drawrect of UIActivityIndicator.
Study:
I have looked into the code of third party activity indicators. Most of them use CABasicAnimation and call repeat always on animation. While few work directly use NSTimer to repeatedly call drawrect with a small delay. Their code works because there is a small delay in calling drawrect and the method drawrect in itself is light weight.
None of it won't take the loads off the main thread but rather they carefully place load on main thread enough to keep their animation going yet keeping main thread free to handle touch events
What I want to know:
1 - Is this strategy to implement activity indicator is correct? or statement like this
self.timer =[NSTimer timerWithTimeInterval:0.1 target:self
selector:#selector(setNeedsDisplay) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
in one of the third party activity indicator that I saw has any special effect?
2 - If I run CABasicAnimation/transaction and repeat the animation forever will it have any special effects on the load of main thread compared to repeatedly calling setNeedsDispaly/drawrect manually?

I'm not sure whether it will help to implement your own activity indicator, but the system one UIActivitiyIndicatorView is just a UIImageView with an array of 12 images that replace each other over time.
Apple made a pretty neat trick by making their spinner discrete. It allowed them to have a simple implementation that doesn't create any computational load on CPU.
UPD
Returning to the things you want to know:
1 - It's not, because implementing manual frame drawing in drawRect is fully done by CPU. And 2 - I can't say for sure, but if one believes what Apple says in documentation and videos about Core Animation it is heavily optimised and runs on Metal or at least OpenGL underneath, so leverages power of GPU.

Related

Alternative to runUntilDate for waiting until animation is complete

We have an application that has some code similar to the following:
[UIView animationWithDuration: 0.7f animations {
// Some animations
}];
[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.7f]];
To wait for an animation block to complete and continue executing code as if the animation block were essentially synchronous. This has worked in all past versions of iOS, but does not seem to be working properly on iPhone 12 devices running iOS 15.
Is there an alternative to this approach to waiting for an animation to complete using semaphores, dispatch barriers, or something similar? We tried moving the code that should run after the animation into the animation completion block, but it is difficult to do so because the logic is complex and there are several levels of nested conditionals. Ideally, we would like to find something that works in a similar manner to runUntilDate by yielding the main thread for a specific period of time without blocking it and then continuing execution once the asynchronous animation is completed.

What is the best way to ensure the main thread is blocked while a background thread is processing?

Consider the following case:
Main Thread -----UIView info \ --------------------------------------- / Button Updated ------
\ (Some Event) / (Some Event)
\ /
BG Thread ---------------------Validate UIView info-----Update Button------------------------
On the main thread, a UIView is present
UIView makes a dispatch call is made to a background thread
In the BG Thread, the UIView's info is validated.
BG Thread makes a dispatch call to a UIButton on the main thread to update it.
My question is - notice how there is a gap between UIView info and the UIButton which means that the app could technically be updated during this time. How can I possible stop this gap? Essentially, from the BG thread, block the Main Thread till the call comes back?
You cannot and must never block the main thread. If you do, the interface will freeze and, if the blockage lasts too long, the WatchDog process will kill your app dead before the user's very eyes.
You can give the user a sense that something is going on, and discourage the user from doing anything, by, say, turning off user interaction and putting up a spinner. But in general, yes, multithreading is hard; you must be prepared for the possibility that you will come back onto the main thread when the app has changed state.
Rather than block a main thread, disable user input controls in your view until the validation is complete, then re-enable them.
It would also make sense to add an activity indicator with hidesWhenStopped set to true; it will show the user that there's background work in progress if you start it when the background work starts, and stop it when validation is complete.
If there's ever a chance the background process could hang or take longer, e.g. if it's making a network request, you might show/enable a cancel button and a way to terminate it.
Showing activity indicator and possibly providing a cancel button both require that the main thread keep running, so definitely don't block it!
Your button should not be updated in the background. You should always modify UIKit components on the main thread.
You should also never block the main thread, what you're looking for is the show the user an indication that a background process is active. 'UIActivityIndicatorView' might be a good thing to show the user, you could also disable user interaction on the view to prevent the user from touching anything if it's critical for them to wait until the operation is complete but not recommended.
Yes, you should never block the main thread and update UI only on main thread.
That said - show a spinner / activity indicator while busy on the background.
Think carefully about the UI and e.g. present something so the user can not change something while you are busy with e.g. dialog or popover or something like that.
In practise this often becomes more a question of UX than blocking.

Long-running task in application:didFinishLaunchesWithOptions:

I have some long-running startup tasks (like loading Parse objects from local data storage) in my app. Those tasks should be finished before the interface starts to appear. The app was originally created using storyboards, so the interface starts to appear automatically after application:didFinishLaunchesWithOptions: method finishes. I can't block main thread because Parse SDK fires all it's callbacks on main thread (so blocking results in deadlock). I also need to delay return from application:didFinishLaunchesWithOptions: to finish setup. So what I did is:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Dispatch long-running tasks
dispatch_group_t startup_group = dispatch_group_create();
dispatch_group_async(startup_group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Perform some long-running setup here
});
// Run main run loop until startup tasks finished (is it OK to do so?)
while (dispatch_group_wait(startup_group, DISPATCH_TIME_NOW))
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
return YES;
}
Is it the proper usage of NSRunLoop? Are there any potential caveats? Can you propose more elegant (preferably GCD) solution?
UPDATE:
Loading Parse object from local data storage (i.e. loading tiny file from SSD) is not so long operation as loading something from the web. The delay is barely noticeable, but long enough to trigger warnBlockingOperationOnMainThread, so UX is not an issue.
The real issue is (infrequent) crashes caused by spinning another main runloop above regular main runloop, which sometimes leads to reentering UIApplication delegate methods, which apparently is not thread-safe.
Of cause introduction of splash screen is an obvious solution, but I was looking for a something more simple and elegant. I have a feeling it exist.
While this technically works depending on what your setup is doing (for example web service calls) the application may never launch or launch with unexpected results.
The more user friendly method would be to add a "loading" view controller to your storyboard which would be your landing view. Perform your long running setup here while providing the user with information/status/whatever is appropriate, then push your original view controller.
Imho I think it's better to do your stuff in your main View Controller and show something to user like a custom spinner: in this way you can download your data and the user know what happens.
It's never a good idea take so long time to launch an app with a blank screen or just a launch screen with no info for the user.
This is creative but a really bad idea. You should make your UI load in a failsafe state - in other words, assume the network is not available/slow, and show a view that won't explode if it doesn't get data before being rendered. Then you can safely load data on a background queue and either update the view or transition to the data-centric view once it is done loading.
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
//Load your data here
//Dispatch back to the main queue once your data is loaded.
dispatch_async(dispatch_get_main_queue(), ^{
//Update your UI here, or transition to the data centric view controller. The data loaded above is available at the time this block runs
});
});
Lagging the return of didFinishLaunchingWithOptions: is really a bad idea.This will give the bad impression of your app and its a bad design.
Instead you could land in the view controller and show a overlay view that shows the progress or activity you are doing when completed you could dismiss it.This is the right approach.
Here is a link to Third party library that eases showing/hiding the overlay view and setting some text on the view.
You can find so many libraries like this just google iOS HUD.HUD means Heads Up Display or you can design the custom thing yourself using UIAnimation easily.
Generally speaking,
I have to appreciate that code you have written in the sense you have stopped the current flow or run loop using While and NSRunloop.
May be you can alter your code little bit and use in some other place of your program to stop the current flow or runloop and wait for something.
NSDate *runloopUntill = [NSDate dateWithTimeIntervalSinceNow:0.1];
while (YourBoolFlag && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode runloopUntill])
The following apple documentation will give you deep insights in NSRunloop 1 2

Constantly refreshing background image of UIImageView IOS

How would I constantly refresh a background image of my UIImageView, while also still listening for touches and input? What I am looking for is the iOS Objective C equivalent (in java) of creating a new thread apart from the main thread, and having that thread be devoted to updating the background picture as fast as possible. Thanks!
It depends on where the new images are coming from. If they are a cycle of known existing images in your app bundle, just make this an animated image view (or an animated image) and the cycle will happen automatically.
If you need to run code e.g. to get out to the network periodically, then just start a repeating NSTimer. It calls you on the main thread, but your main thread code will be very brief indeed, and networking takes place asynchronously (unless you mess that up deliberately). Just make sure that when you actually set the image, you step out to the main thread, as you must never touch the interface in any way except on the main thread.

How force the UI message loop to flush in iOS before continuing

I have an application that experiences a brief delay when switching views, on the order of 500-1500ms. The change in views is subtle, so I need to provide feedback to the user that something DID just happen.
I would like to use a "Loading" overlay. Unfortunately, the work that is occupying the CPU is related to building the UI, and therefore cannot be moved to a background thread.
Since the work is occupying the main thread, if I add a loading overlay before the other operation starts, it never gets shown because the thread is working on the next workload and won't update the UI until it gets around to it.
Some operating systems have a DoEvents or FlushMessagePump method that can be used in the rare circumstances like these. Is there such a thing in iOS? SetNeedsDisplay() is not what I want, as it will only queue the update in the message pump.
Alternative suggestions are welcome too.
Instead of delaying your loading, you can force the run loop to run:
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate date]];
Show your loading overlay, and then use - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay with a delay of 0 to do your actual loading. This will delay your loading until the screen has a chance to refresh, because it will queue the selector on the main thread's run loop.

Resources