This is a question about iOS memory management when app enters the background.
If I have a var in the AppDelegate with a value:
#interface MyAppDelegate : NSObject {
NSString *userName;
}
// In some place
((MyAppDelegate*)[UIApplication sharedApplication] delegate]).userName = #"StackOverflow"
My question is: when the app enters the background, because the user receives a call or opens another app, is it possible that iOS releases the memory for any reason (so destroying my AppDelegate instance)? So I lose the value of userName?
Programming in Android we have to use persistence mechanisms like Parcelable, using methods of lifecycle like onSaveInstance, etc. But in iOS we don't have to manage values of var for the "iOS garbage collection"?
Maybe the answer can be trivial talking about a String var, but what happens when I stored in the AppDelegate a big Array of UIImages? It will never be release when app enter on background?
Sorry for my bad english and I can't find information about lifecycle iOS, appDelegate and memory management.
You never really know. iOS might need to remove the app from memory to use it for something else. If the user closes the app for just a minute then you might be ok, but the app could be closed for days, and the user may do any number of other things...
So, you should really either:
Save data each time it changes, or
Save data when the app is told that it is going to the background
Option 1. is more safe, because it also saves data against crashes, but it isn't always practical. Generally, save as soon as you can and, if testing shows you need to, later work on making things run better (like by processing and saving to disk on a background thread).
When an app is launched, the UIApplicationMain function is called, this function creates a singleton UIApplication object.
When you declare an iVar like this : NSString *userName; you are creating a strong reference to it ( by default it's strong).
To answer your question : the userName will be released when the UIApplication will be released and because the UIApplication is a singleton, it will be never release until the application is not killed. The application will be killed in many situations :
The user kill the app
The system kill the app (memory,...)
Documentation :
Memory management
iOS App life cycle
Related
I'm using NSUserDefaults in my iOS app to record some specific info about the user's receipt state.
I'd like to confirm:
If the user quits the app, will those defaults remain?
Are they global, for example I'm currently using the following line to either get or set them and across different methods. I just want to be certain the data within it persists - so if I set in method1 then later method2 I use the same line to get, it will have whatever I set in method1:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
If the user quits the app, will those defaults remain?
Yes, they are persistent.
Are they global?
Global in the sense of your whole app: Yes.
Global in the sense of across apps: No. They are in the app's sandbox.
From the NSUserDefaults documentation (see https://developer.apple.com/documentation/foundation/nsuserdefaults?language=objc)
With the exception of managed devices in educational institutions, a user’s defaults are stored locally on a single device, and persisted for backup and restore. To synchronize preferences and other data across a user’s connected devices, use NSUbiquitousKeyValueStore instead.
So for your first question the answer is yes.
Accordind to your second question, doc says:
At runtime, you use NSUserDefaults objects to read the defaults that your app uses from a user’s defaults database. NSUserDefaults caches the information to avoid having to open the user’s defaults database each time you need a default value. When you set a default value, it’s changed synchronously within your process, and asynchronously to persistent storage and other processes.
And even so, it declares that the class is thread safe, so you can be sure about persistent results (for your second answer).
Additionally with #Nikolai Ruhe answer.
if I set in method1 then later method2 I use the same line to get, it will have whatever I set in method1: NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
The UserDefaults class is thread-safe.
Two further points.
The uselessness of synchronize has grown up gradually through versions of iOS. It used to be essential; then advisable; then unnecessary. I can’t give you an exact timeline but a lot of it depends on how far back in iOS you want your app to work.
If you try to test some of these things in Xcode, beware. Up to and including iOS 11, synchronize does not write all the way to disk, but only puts data in a “lazy write” queue. Pressing the Stop button in Xcode (or pressing Run and allowing Xcode to stop the previous running app automatically) shuts everything down abruptly, more abruptly than anything a user can do, and the lazy writes are not written out, but lost. This is confusing while you are resting!! I filed a report on it and was told by Apple that (as Microfot would put it) “This behaviour is by design”.
But just to be clear: that second point does not present a problem for real apps in a real environment, only when you are trying to test “save and restart” scenarios. Waiting 5-10 seconds seems to be long enough for the flushed data to make it all the way to disk.
I tested:
UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil)
which is for putting app on background and it works.
How do I put app back on foreground?
I tried:
UIControl().sendAction(#selector(URLSessionTask.resume), to: UIApplication.shared, for: nil)
But eventually it crashes...
Thank you
Update:
Since you've indicated that you're looking for any technical solution, even those not compatible with the App Store or Apple's terms, this should be possible using the Private API LSApplicationWorkspace: openApplicationWithBundleID. Try something like this:
Create a .h file and set up an interface to the LSApplicationWorkspace class and list the required method. You will need to #import "PrivateHeaders.h" in your bridging header.
//
// PrivateHeaders.h
//
#ifndef PrivateHeaders_h
#define PrivateHeaders_h
#interface LSApplicationWorkspace : NSObject
- (bool)openApplicationWithBundleID:(id)arg1;
#end
#endif /* PrivateHeaders_h */
You should then be able to call this function and pass in the Bundle Identifier of your app as an string.
//
// SomeClass.swift
//
import MobileCoreServices
let workspace = LSApplicationWorkspace()
/**
Launch an App given its bundle identifier
- parameter bundleIdentifier: The bundle identifier of the app to launch
- returns: True if app is launched, otherwise false
*/
func openApp(withBundleIdentifier bundleIdentifier: String) -> Bool {
// Call the Private API LSApplicationWorkspace method
return workspace.openApplication(withBundleID: bundleIdentifier)
}
Original:
What you are doing is likely a violation of the iOS Human Interface Guidelines (although the "Don’t Quit Programmatically" is no longer specifically defined), so as the comments have said, it is not suited to the App Store. Regardless, once your app is suspended in this way, I don't expect that there is a way to resume it programmatically, unless you can hook into a Background Operation to run URLSessionTask.resume, but I have not tested it and am unsure whether it can work.
Apps can be launched (and hence brought into the foreground) programmatically from another app or today extension by using a Custom URL Scheme, or via a Push Notification. It isn't possible to launch the app from the Background Operation via a URL Scheme, since it is part of the UIKit framework, which must be run in the main thread.
In summary, I think your best option is to try to use a Notification. This just means that the user will need to click on the notification to bring your app back into the foreground.
Closing/opening the app should be done explicitly by the user. Any other way of closing or opening the app is not supported by Apple and will be rejected when uploaded to app store. iOS Human Interface Guideline states:
Don’t Quit Programmatically
Never quit an iOS application
programmatically because people tend to interpret this as a crash.
However, if external circumstances prevent your application from
functioning as intended, you need to tell your users about the
situation and explain what they can do about it. Depending on how
severe the application malfunction is, you have two choices.
*Display
an attractive screen that describes the problem and suggests a
correction. A screen provides feedback that reassures usersthat
there’s nothing wrong with your application. It puts usersin control,
letting them decide whether they want to take corrective action and
continue using your application or press the Home button and open a
different application
*If only some of your application's features are
not working, display either a screen or an alert when people activate
the feature. Display the alert only when people try to accessthe
feature that isn’t functioning
Just as a follow up to Jordan's excellent answer I want to give an explanation for why your code works in the first place and why that alone will get your app rejected, even without any functionality to make it active again and bring it to the foreground.
As maddy pointed out in a comment, you're basically calling a method from UIApplication's private API. This works due to the Objective-C runtime's dynamic linking. You might wonder "But I am using Swift, what does that have to do with Objective-C?" The answer lies in #selector mechanism. A Selector is basically just a symbol that the Objective-C runtime looks up in a table to get a method it invokes (for you). This is why it's technically not correct to say you "call a method" when you do something like myObjectInstance.someMethod(). The correct way to phrase that would be to "send a message" to the object, because that's what is happening in the runtime. The target-action mechanism is build around that. The sendAction(_: Selector?, to: Any?) method does the same thing. So in effect your code does the following:
Get the symbol that corresponds to URLSessionTask's suspend() method.
Tell the shared instance of UIApplication to invoke the method that it has for that symbol.
Now usually that would result in a crash with the typical "unknown selector sent to instance..." error message. But here, by sure coincidence UIApplication also has a method for that instance (or rather, the runtime also has one of its methods listed in its table for that symbol). You kind of "found" a method that is not declared in its public header. You successfully circumvented a compile-time check for this and invoke a method that is part of a private API. This is explicitly forbidden in the Apple Developer Program License Agreement
Besides all that, I would strongly advise against trying to design an app that way in the first place. As maddy pointed out it's also likely considered to violate the HIGs. Even if you're not trying to do anything malicious and properly explain the feature in your app's description, that won't make Apple let it slide (I assume). Personally, as a user, I'd also find it annoying if the app did something the system already has a specific mechanic for in a different manner, at least in terms of app's coming to background and foreground.
I don't think it can be done without user interaction
The option is you can generate a push notification to tell the user to bring the application to foreground
When the operating system delivers push notification and the target application is not running in the foreground, it presents the notification.
If there is a notification alert and the user taps or clicks the action button (or moves the action slider), the application launches and calls a method to pass in the local-notification object or remote-notification payload.
I tested:
UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil)
which is for putting app on background and it works.
How do I put app back on foreground?
I tried:
UIControl().sendAction(#selector(URLSessionTask.resume), to: UIApplication.shared, for: nil)
But eventually it crashes...
Thank you
Update:
Since you've indicated that you're looking for any technical solution, even those not compatible with the App Store or Apple's terms, this should be possible using the Private API LSApplicationWorkspace: openApplicationWithBundleID. Try something like this:
Create a .h file and set up an interface to the LSApplicationWorkspace class and list the required method. You will need to #import "PrivateHeaders.h" in your bridging header.
//
// PrivateHeaders.h
//
#ifndef PrivateHeaders_h
#define PrivateHeaders_h
#interface LSApplicationWorkspace : NSObject
- (bool)openApplicationWithBundleID:(id)arg1;
#end
#endif /* PrivateHeaders_h */
You should then be able to call this function and pass in the Bundle Identifier of your app as an string.
//
// SomeClass.swift
//
import MobileCoreServices
let workspace = LSApplicationWorkspace()
/**
Launch an App given its bundle identifier
- parameter bundleIdentifier: The bundle identifier of the app to launch
- returns: True if app is launched, otherwise false
*/
func openApp(withBundleIdentifier bundleIdentifier: String) -> Bool {
// Call the Private API LSApplicationWorkspace method
return workspace.openApplication(withBundleID: bundleIdentifier)
}
Original:
What you are doing is likely a violation of the iOS Human Interface Guidelines (although the "Don’t Quit Programmatically" is no longer specifically defined), so as the comments have said, it is not suited to the App Store. Regardless, once your app is suspended in this way, I don't expect that there is a way to resume it programmatically, unless you can hook into a Background Operation to run URLSessionTask.resume, but I have not tested it and am unsure whether it can work.
Apps can be launched (and hence brought into the foreground) programmatically from another app or today extension by using a Custom URL Scheme, or via a Push Notification. It isn't possible to launch the app from the Background Operation via a URL Scheme, since it is part of the UIKit framework, which must be run in the main thread.
In summary, I think your best option is to try to use a Notification. This just means that the user will need to click on the notification to bring your app back into the foreground.
Closing/opening the app should be done explicitly by the user. Any other way of closing or opening the app is not supported by Apple and will be rejected when uploaded to app store. iOS Human Interface Guideline states:
Don’t Quit Programmatically
Never quit an iOS application
programmatically because people tend to interpret this as a crash.
However, if external circumstances prevent your application from
functioning as intended, you need to tell your users about the
situation and explain what they can do about it. Depending on how
severe the application malfunction is, you have two choices.
*Display
an attractive screen that describes the problem and suggests a
correction. A screen provides feedback that reassures usersthat
there’s nothing wrong with your application. It puts usersin control,
letting them decide whether they want to take corrective action and
continue using your application or press the Home button and open a
different application
*If only some of your application's features are
not working, display either a screen or an alert when people activate
the feature. Display the alert only when people try to accessthe
feature that isn’t functioning
Just as a follow up to Jordan's excellent answer I want to give an explanation for why your code works in the first place and why that alone will get your app rejected, even without any functionality to make it active again and bring it to the foreground.
As maddy pointed out in a comment, you're basically calling a method from UIApplication's private API. This works due to the Objective-C runtime's dynamic linking. You might wonder "But I am using Swift, what does that have to do with Objective-C?" The answer lies in #selector mechanism. A Selector is basically just a symbol that the Objective-C runtime looks up in a table to get a method it invokes (for you). This is why it's technically not correct to say you "call a method" when you do something like myObjectInstance.someMethod(). The correct way to phrase that would be to "send a message" to the object, because that's what is happening in the runtime. The target-action mechanism is build around that. The sendAction(_: Selector?, to: Any?) method does the same thing. So in effect your code does the following:
Get the symbol that corresponds to URLSessionTask's suspend() method.
Tell the shared instance of UIApplication to invoke the method that it has for that symbol.
Now usually that would result in a crash with the typical "unknown selector sent to instance..." error message. But here, by sure coincidence UIApplication also has a method for that instance (or rather, the runtime also has one of its methods listed in its table for that symbol). You kind of "found" a method that is not declared in its public header. You successfully circumvented a compile-time check for this and invoke a method that is part of a private API. This is explicitly forbidden in the Apple Developer Program License Agreement
Besides all that, I would strongly advise against trying to design an app that way in the first place. As maddy pointed out it's also likely considered to violate the HIGs. Even if you're not trying to do anything malicious and properly explain the feature in your app's description, that won't make Apple let it slide (I assume). Personally, as a user, I'd also find it annoying if the app did something the system already has a specific mechanic for in a different manner, at least in terms of app's coming to background and foreground.
I don't think it can be done without user interaction
The option is you can generate a push notification to tell the user to bring the application to foreground
When the operating system delivers push notification and the target application is not running in the foreground, it presents the notification.
If there is a notification alert and the user taps or clicks the action button (or moves the action slider), the application launches and calls a method to pass in the local-notification object or remote-notification payload.
In my app i have several "Managers" objects that handle stuff for me, for example:
FacebookManager - handle all the Facebook methods of my app
ApiManager - handle all our product API connection method
etc..
All those managers should be initialise at when the app starts and terminated once the app is terminated.
Naturally because the AppDelegate handle the start and end of my application i thought to put those instances there, so all other's app's part will have a way of getting them as needed.
Is it a good practice or should i use the app delegate to assign a single-tone in a static variable for each of the managers ?
Some background
I'm working on an iOS app where we want the state of the application to be preserved.
Before this app is out, iOS 7 is likely to have been released or is soon to be released and the majority seems to have moved away from iOS 5. We have therefore decided to develop for iOS 6 an onwards.
In iOS 6 there are some really nice functionality for preserving state. Just give all views in the storyboard unique IDs and implement these two functions in the "AppDelegate":
- (BOOL)application:(UIApplication*)application shouldSaveApplicationState:(NSCoder*)coder;
- (BOOL)application:(UIApplication*)application shouldRestoreApplicationState:(NSCoder*)coder;
iOS will then "automagically" preserve the navigation history of the app. The methods:
- (void)encodeRestorableStateWithCoder:(NSCoder*)coder;
- (void)decodeRestorableStateWithCoder:(NSCoder*)coder;
can then be used to store and retrieve data.
No problems there it works without issues. But, the methods that save the state are only triggered when the app entered the background.
Let's say we have one NavigationController with four ViewControllers: A, B, C and D. The user navigates from A to B, in B he switches over to Safari to google something. The application state is saved in B. The user then switches back to the app and navigates on to C and then to D. In D the app unfortunately encounters an exception and goes down. When the user restarts the app, iOS will try to restore the saved state. This state however, was saved in B. Which means that when the app launches, it doesn't start from the beginning, not where the user left it (D) and not even the previous view (C) but in B.
A possible solution
The above scenario could be avoided if the app saved its state at every new view. However there aren't (as far as I know) any public methods to trigger the state preservation process.
I have examined the call stack while debugging and found out that iOS calls the following method on the UIApplication object in iOS 6:
_saveApplicationPreservationState:
and the following method in iOS 7:
_saveApplicationPreservationState:viewController:sessionIdentifier:beginHandler:completionHandler:
There also seems to be another method that calls one of the above depending on the iOS version:
_saveApplicationPreservationStateIfSupported
By invoking this method like this:
if ([[UIApplication sharedApplication] respondsToSelector:#selector(_saveApplicationPreservationStateIfSupported)])
[[UIApplication sharedApplication] performSelector:#selector(_saveApplicationPreservationStateIfSupported)];
I can see that the expected methods are called.
The actual question
If I would go with the above solution could that get my app rejected from the App Store? I mean technically it's not a private method, it's just not exposed. By wrapping the call in "respondsToSelector" the app won't crash if the APIs are changed, it just won't save the state as often. But if it could get the app rejected it's not an option.
Or is there any other way to manually invoke the state preservation process other than the one described above? I would be nice to be able to use the built-in functionality rather than building a custom solution that saves the state to NSUserDefaults.
Event if the question is already 2 years old, i'll try my luck. Btw op, i guess, you'll have most likely solved it by now.
You're gonna for sure get rejected. They are scanning the sourcecode for methods you are calling. I had this several times, back when i was using the UDID methods. Hope it helped.