Strategy to launch a task - and avoid race conditions - from the App Delegate - ios

I'm developing a small app on top of core data. At startup, I need to launch a maintenance task - fast but important -. To run this task, the app delegate must open a UIManagedDocument, and perform various checks on it. The app views must not start until the checks are completed. Because opening a UIMD is asynchronous, the app delegate isn't done when the first UIview tries to access the doc. The application then crashes due to a race condition because the app delegate and the view are trying to open the doc at the same time, while the doc state isn't yet finalised.
I'm using a storyboard, so segues are pretty much in control of the OS... Next time, I'll do it all manually..
What would be your recommendations ?
Note:
1)I can not perform the task when the app. goes into background state, because if it is brought back up again, avoiding inconsistent states between the underlying database and what's displayed in the view will be very tedious.
2)For the same reasons, performing the maintenance task during normal execution is not easily done.
3) All views access the UIMD via a singleton, according to the code proposed here
Setting a mutex lock in the UIView isn't my preferred route, because the screen remains black - no data -, while displaying the tab bars, until it is released by the app delegate.
Is there a way to have the app delegate wait for a signal before it hands the control over to the UIViews ? In this case, are there any gotchas ? I suspect this is probably not the recommended way to do, and iOS might kill the app if the delegate stays too long waiting for the maintenance task to complete. What would be "too long" in this case?

You could do it more elegant way. The first view the user will see must be some kind of SplashView - just an image with progress indicator. It should stay on top while your task is going on. It's not too important how you're showing this view. You can set it as the first in your storyboard or just create it manually in applicationDidFinishLaunching message.
Then in your task send a NSNotification when it's about to finish and in the observer in your AppDelegate just hide your SplashView and present your first view with valuable content.
You can even add some visual effects for transition between those views and your app will look really great! :)

Related

Quit the application on a specific view

I have a doubt:
I have an app with 10 views. I want that, if the user is on View1 and sends the app to the background, it terminates the application (exit (0)). But I wanted this to happen only on View1, on the other screens, if the app goes to the background and then returns, it will continue where it left off.
What can I do?
Apple's guidelines seem to be strictly against terminating your app programmatically (for example, with exit()); it would go against what iOS users expect of how apps work.
Instead, I recommend the following approach:
When the app is sent to the background (applicationWillResignActive(_:) is called), check which view controller is currently being displayed.
If it's such that you wish to start over next time the app is brought to the foreground, just reset the app window's root view controller to whatever the initial view controller of your app is (typically, it involves reloading the inital view controller from Main.stroyboard, but your setup could be different).
You can not choose at runtime whether your app goes to the background or is terminated when the user presses the home button ("multitasking"); that is set in stone in your Info.plist file at build time.
Also, remember that even if you are in the screen that you wish to preserve when the user resumes, your app might be terminated by the system while it is in the background (to reclaim scarce system resources), so in that case it will still start from the initial screen. To prevent this, you might want to check out the APIs for state preservation and restoration.
Here is another SO question asking how to find the identity of the current view controller. Why not query the current view when you receive applicationWillResignActive indicating that your app is going to move to the background and then choose the action you want?
As far as I understand your description Preserving and Restoring State is what you are looking for.
Excerpt from Documentation:
The preservation and restoration process is mostly automatic, but you need to tell iOS which parts of your app to preserve. The steps for preserving your app’s view controllers are as follows:
Required
Assign restoration identifiers to the view controllers whose
configuration you want to preserve; see Tagging View Controllers for
Preservation.
Tell iOS how to create or locate new view controller objects at
launch time; see Restoring View Controllers at Launch Time.
Optional
For each view controller, store any specific configuration data needed to return that view controller to its original configuration; see Encoding and Decoding Your View Controller’s State.
Here is a link to Preserving Your App’s Visual Appearance Across Launches

Should I avoid UI when iOS app is launched in background

My app supports background location updates (specifically with significant location monitoring).
Do I need to prevent UI (via controllers etc.) from loading when I identify that the app is in background (application.applicationState == UIApplicationStateBackground)?
My intention is to avoid heavy UI loading (it's a BIG app) in background, that might waste all the limited time I have in background to actually respond to the location update.
For example (in ObjC, but the question is also for Swift), let's say I have some RootViewController which initializes and holds the whole controllers/view hierarchy, should I, in the viewDidLoad do:
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
// Root view controller loaded while in background, so doing only background stuff
[self justDoBackgroundStuffWithoutUI];
} else {
// Root view controller loaded normally, so loading normal UI
[self initializeUIAndChildControllers];
}
? Or should I just "trust" iOS to ignore all of those UI tasks, because it will know it's in a background state?
You can ignore those and let the OS handle that, just be careful if you have long running BG tasks, they may or may not have time to complete so it is best to very careful as it won't allow you to run tasks forever.
Updating UI in background is unnecessary and confusing and non-efficient.
unnecessary because there is no added value to the user who's not using the app. A simple doing of things in viewWillAppear would suffice.
confusing because users maybe expecting to see changes themselves as the screen is appeared, I don't know of your app, but this is more of a business choice. Maybe you have something similar in the manner of displaying changes like gmail/whatsapp and you want users to see the new emails/messages.
non-efficient because you are just doing something battery consuming too early. You even said "heavy UI loading". What happens if the location changes again and again and again? could the changes be overridden in the sense that they are no longer necessary or you will always need the changes regardless.
To summarize: I'm not saying don't do any UI updates, every app has a sweet spot. Likely you won't need to do most of them and lazy loading the changes ie upon loading screen seems to be the better way. Though I'm sure there are some advanced guidelines which I'm unaware of. Hopefully other answers would will come.

Having trouble with AppDelegate didFinishLoadingWithOptions execution order/timing

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.

iOS Launch Screen Animation Time

It seems like the fade animation between the launch screen and my first view is really slow.
I don't think it used to be like that. Is there a way to control the speed of that transitional animation?
I looked at some apps on my phone and the launch screen doesn't fade as slowly as mine. What things could I have done to affect that?
(No I don't have slow animations turned on, only the fade animation is slow)
In WWDC 2012 video iOS App Performance: Responsiveness they enumerate a whole list of issues that have impact on the app startup time, ranging from attaching to unnecessary frameworks, optional linking to frameworks that you really could make required, the use of static initializers, overly complicated initial scenes, too much information stored in preferences, etc.
That video covers a range of topics, like the above, which impact startup time. In the demonstration, they show how one might benchmark the startup time. Unfortunately, in my experience, there's a good chance that you might not be able to do anything to fix this issue (e.g. you need certain features and therefore need certain frameworks), but it's still an illuminating video and it might give you some ideas of things you can try to alleviate the start-up performance issues.
If your app splash screen show more time, so please check following things in your app.
1. AppDelegate.m
in didFinishLaunchingWithOptions method have you called any heavy method which takes more time for finish task if yes then change that method location, basically in appDelegate class don't write any heavy methods.
2. first view of your app.
check viewDidLoad() method, if you call many method or any heavy method from here then your launch image will show still your control not come out from viewDidLoad method, so if you want call any methods at view launch time then call them from viewWillAppear or viewDidAppear method (in viewDidAppear method don't call any UI related methods)
I never figured out what was going on here, but the next day when I started up xCode and the simulator it was back to the normal loading time.

Strategies for "realistically" testing low memory conditions in iOS Simulator

I'm at a point in the development of my little app where I'm implementing didReceiveMemoryWarning and such, but I feel like I don't have a good grasp on the best way to test what I'm implementing.
First off, apparently in the simulator, didReceiveMemoryWarning isn't triggered for apps that aren't in the foreground until that app is brought back into the foreground, according to this question - actually, that matches my own experience, but I was prone to ignore it because it doesn't really make sense. (Why would I want to delay clearing up my memory until I'm coming back into the foreground and probably about to need that data again?) Does this match the behavior of actual hardware? If so, does it make sense to cleanup tasks in applicationWillEnterBackground in addition to didReceiveMemoryWarning?
Second, generally, what is a good strategy for triggering the "Simulate Memory Warning" menu item in the simulator in such a way as to trigger memory warnings in a manner that they're likely to happen on the actual hardware?
Q1: Does this match the behavior of actual hardware?
A: Yes, the simulator and the actual device behave the same in this regard (e.g. if your app is suspended and is not running a background task, it does not receive applicationDidReceiveMemoryWarning).
Q2: Does it make sense to cleanup tasks in applicationWillEnterBackground?
A1: Only insofar as that your app must prepare itself for a complete restart. If your app remains suspended and the user does a lot of things with other apps, your app may become fully unloaded - even though the app may still be visible in the task switcher, when the user activates it the next time, your app delegate will receive application:didFinishLaunchingWithOptions: (not applicationWillEnterForeground:).
A2: I wouldn't bother with cleaning up memory, but you should make sure that by the time applicationWillEnterBackground has finished, any user preferences and other stuff are saved so that the app knows where to take up again when it is activated the next time.
Q3: What is a good strategy for triggering the "Simulate Memory Warning" menu item?
A1: I doubt whether a general strategy can be formulated since every app is different.
A2: To use a specific case: In my app, I don't use the app delegate's applicationDidReceiveMemoryWarning to relieve memory pressure, instead I rely on the fact that when a memory warning is sent, iOS also invokes viewDidUnload on certain view controllers that are not in use. Therefore I generally use the memory warning simulation to test whether my view controllers' viewDidLoad and viewDidUnload are balanced correctly.
A3: Concrete example: My app uses tabs, so a favorite scenario of mine is to 1) display the tab with the view controller (VC) under test; 2) navigate to a different tab; 3) simulate the memory warning (iOS invokes viewDidUnload in the VC under test); 4) navigate back the original tab (iOS invokes viewDidLoad in the VC under test).
A4: If you want to use a similar approach, then you have to figure out for your app which views might be unloaded by iOS. In my (limited) experience these tend to be views that are unrelated, such as views on different tabs.
Here's my answer to your second question - it also kind of answers your first question:
I've used the "Simulate Memory Warning" feature to effectively reproduce issues where a child view controller consumes enough memory such that its parent must free up resources.
For example, consider a child view controller that displays the camera/photo library (i.e., UIImagePickerController)). This uses a significant amount of memory. If the picker controller consumes more memory than is available, the parent's view controller will be unloaded, and once the parent view controller is shown again (when the child is popped), the parent's viewDidLoad will be called again. This means that any variables set in viewDidLoad will be set again, potentially with memory leaks or bad pointers.
The "Simulate Memory Warning" helps to find these problems. You can go into the child view controller, select "Simulate Memory Warning", then pop the view controller and check for problems.
Note that the "didReceiveMemoryWarning" is more relevant (imho) to let you know it happened than actually trying to release memory at that point.

Resources