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
Related
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.
I'm having an issue with this code (I have it in didFinishLaunchingWithOptions). I need it to finish executing the code inside before the app continues because it sets up some critical things in core data. It sends this to the background though and starts running the rest of the app and it doesn't finish quick enough for the data to be usable.
DataManager.getDataWithSuccess { (data) -> Void in
//code to execute here
}
How can I force the app to wait for this code to finish before moving on?
You shouldn't block the didFinishLaunchingWithOptions method from returning so that it can wait on an asynchronous task. It's crucially important to note that iOS applications are only given a limited amount of time to complete launching before the application is killed by the operating system.
An approach I have used in the past when waiting on asynchronous things to happen that need to happen before I really launch my app is to create a LaunchViewController. The interface for this view controller matches perfectly to the app's splash screen. From an end-user perspective, you can't even tell we've left the splash screen.
Here, we do any set up code such as asking your DataManager to get data. Then, when it (and any other set up actions) completes, you simply present the next view controller in much the same way you'd move between any other view controllers.
A huge positive side effect here is that you can have much nice looking animations from your splash screen into the first screen of your application.
I have an iOS application that has an NSTimer which fires every 5 seconds. This then posts a notification, telling several controllers that they must now recalculate some data and then update their UI.
When this all happens on the main thread, scrollviews can become jittery as the data is processed.
Using GCD, I have wrapped the code called when a notification is posted:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Code here
dispatch_async(dispatch_get_main_queue(), ^(){
// Reload tableviews and UI
};
};
Unfortunately, this leads in certain cases to several threads accessing the same instance variables. This causes my application to crash as sometimes it ends up mutating an array being enumerated in another thread.
Wrapping the code in a synchronised block prevents this.
When the user scrolls the tableview, several methods are called to calculate the height etc, on the main thread. The code within the background thread is working on the code needed by the main thread. As such, this can usually further cause crashes
I have tried setting the variables to 'atomic', but this doesn't solve the issue. Ideally, I don't want to have the main thread waiting on the background thread, but I am not sure how to best resolve this issue given that they need the same data?
This is pretty classic multithreaded programming issues. There are a number of ways to solve it with basic locks (#synchronized blocks), reader/writer locks, etc but the problem is often that you can't control when the user is going to scroll or take other action. If you #synchronize, you have to do it anywhere that data is touched, including your UITableView data source methods. That can lead to stuttering if the background processing happens to be in the middle of something.**
Personally, I would use an immutable snapshot mechanism.
Have the background thread produce the results, then include just the data the UI needs to display in the notification data as an immutable snapshot (copy). That way the background thread never modifies the data the UI is currently reading for display. How you would implement this is highly dependent on how much data you are talking about and the form it takes, but the safe way would be to have copies of your classes with readonly properties. Alternatively, you can use a "frozen" flag. Make a copy, then set frozen = YES on the copy. The UI thread will only ever see "frozen" or readonly objects coming from the background thread.
The benefit is the UI never causes the background thread to stall and there are no locks required. The downside is increased memory usage, though if the amount of data is large you can use copy-on-write mechanisms to allow the background thread and UI thread to share the data, even though logically the UI thread has a completely separate copy.
** Note: In most applications, you don't have this sort of continuous background processing going on so those apps can use simpler mechanisms. This is typically a form of message passing where the background thread finishes its task and "passes the message" to the UI thread (passes the results). At that point the background work is finished so there is no concurrent modification happening.
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.
In my iPad App, am connecting to a web service. Whilst connecting to it, am displaying the progress activity indicator and a corresponding message to the user in a label (the label is in a subview and am adding the subview to the current view).
After this line of code (which calls a method to add the subview to the view), am invoking the method to call the web service. However, the web service call is getting executed first, and then only the user-information subview is displayed.
Is there any way to say that I want to 'continue displaying' the alert view even while the execution continues to the next line of code?
// Calling method to add info/alert subview to current view [self displayUserMessage];
// Connect to Web Service [self connectToWebService];
I'm not sure if I totally understand your question. Also it's far more easy to understand if you provide some code after your explanation... Anyway what I understand is that you are connecting to a web service and showing some info while the connection is on going?
Remember that if you don't want to hang your user interface you need to send the webService Connection in another thread, so you can keep the main thread free. You can do so using GCD.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[self connectWithWebService];
});
Then depending on the architecture of the web service, you can use a delegate o maybe a completion block to show some messages (info/alert) to the user. In that case remember that anything related to UI should run on the main thread. So as I said before depending on your architecture you should do something like this
dispatch_async(dispatch_get_main_queue(), ^{
// Show UI Changes
});
The UI should update properly while the webService method is running on background.
If you want asynchronous connections its easier to go with NSURLConnection's sendAsynchronousRequest:queue:completionHandler:..
you can display your alert before calling it and dismiss it in the completion handler.