How to Persist Application State in cocoa - objective-c

I need to store the state of application at application termination time, so that when user re-run app, App run from the state in which it was closed last time. It is some kind of restoring app but restore methods called when app close unexpectedly. But i need to restore app each time when it close unexpectedly of user close it manually.
I just need to store the App UI not the data of application.
Any idea would be helpful for me.
Thanks

You can persist the state in any of the available methods like:
i. NSUserDefaults
Example:
//saving
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:#"testBool"];
//retrieving
[defaults boolForKey:#"testBool"];
ii. Serializing the state object.
iOS 4 iPhone Data Persistence using Archiving
Correct way to save/serialize custom objects in iOS
iii. Saving as a plist file
Example:
NSMutableDictionary *stateDictionary = [NSMutableDictionary dictionary];
//set state
...
//saving
[stateDictionary writeToFile:<filePath> atomically:YES];
//retrieve
stateDictionary = [NSMutableDictionary dictionaryWithContentsOfFile:<filePath>]
iv. Using sqlite or Core-Data
(Most probably not needed unless the
state of your app is in some kind of a object relational model)
UPDATE:
For preserving the UI state of windows,
Check this link and under the heading USER INTERFACE PRESERVATION.

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
//saveData
return NSTerminateNow;
}
If you want to save NSWindow position , you can use [window saveFrameUsingName:#"myWindow"];
and use
[window setFrameAutosaveName:#"myWindow"]; # the app launch.

Related

NSUserDefaults not writing to disk

I have a small background Mac (menubar) app which uses preferences for one simple value (string). When I launch the app from Xcode, it will almost always report the pref as being set, even when the plist does not exist in ~/Library/Preferences. Without making any codebase changes, it appears to occassionally write the prefs file upon subsequent launches. It does have a helper app which runs normally, and a Today extension, if it matters. I've tried using the [defaults synchronize] command, it doesn't do anything. I have other apps which work fine. I don't access or set NSUSerDefaults (including bindings) anywhere else in my project
Currently, my app is NOT sandboxed.
some sample code:
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
[standardDefaults synchronize];
NSLog(#"value for key is %#", [standardDefaults valueForKey:#"firstTime"]);
//this always reports back as "Accepted" even when no pref exists
if ([[standardDefaults valueForKey:#"first"] isEqualToString:#"Accepted"])
{
NSLog(#"accepted");
//always seen
}
else
{
//rarely seen except in complete random times
NSLog(#"first time");
#try
{
NSLog(#"alert");
[standardDefaults setObject:#"Accepted" forKey:#"first"];
[standardDefaults synchronize];
}
#catch (NSException *exception)
{
NSLog(#"exception is %#", exception);
}
#finally
{
}
}
UDPATE: I now see this on new apps, non-sandboxed. Bizarre. I created a brand-new mac/Cocoa app, and used this code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
[standardDefaults synchronize];
NSLog(#"value for key is %#", [standardDefaults valueForKey:#"firstTime"]);
if (![standardDefaults valueForKey:#"firstTime"])
{
NSLog(#"settings value for key");
[standardDefaults setValue:#"Whatever" forKey:#"firstTime"];
[standardDefaults synchronize];
}
NSLog(#"value for key is %#", [standardDefaults valueForKey:#"firstTime"]);
// Do any additional setup after loading the view.
}
The first time, it reports as missing. Every attempt afterwards, tt immediately registers as the pref existing and reporting as "whatever", even when the pref file is most definitely not there.
The file is normally periodically written by cfprefsd. All times you read/write the values you're actually talking to cfprefsd, not to the file on disk.
When you synchronize, it causes cfprefsd to get the values that you've set, again, not guaranteeing that it is written to disk.
The condition you've stated seems to indicate that the preference was set the first time round, and even though you've not seen the file appear on disk, it's reading the values. This is the expected behaviour.
If you want to test resetting the preferences before writing, you need to clear them out, using something like:
NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];
I believe that since the introduction of cloud preference syncing, it's become a more noticeable issue that the file doesn't appear. Modifying the file has never been considered stable.
If you want to delete the preferences from the command line, you should use defaults delete (I don't have a mac at hand to give you the exact command line for this, though).

iOS Sync in background thread

i need some advice concerning background synchronisation...
At the moment I have an app which starts sync in background after app has started and then continues via timer every 2 minutes. Now I want to enable the user to manually start sync process in case of impatience... but ensure that there is no currently running sync.
If there is an sync running the users request should be "redirected" to this sync if not a new sync shall be started, but prevent the auto sync from being starts while manual sync is in progress.
Unfortunately I don't have an idea on how to do this....
Thx for your help!
BR Boris
You do not give much detail about how you are trying to do this, but a simple solution should be this one:
add a flag to your controller: isAlreadySyncing;
when your start syncing, set the flag; reset it when finished;
when the user starts syncing out of impatience, check the flag: if it is true, do not do anything; otherwise, start syncing (and also set the flag, as above);
when your timer fires and tries to sync, check the flag: if it is set, do not to anything.
Hope this helps.
I think you have different controllers.
What you need to do is use NSUserDefaults.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setValue:#"YES" forKey:#"is_sync_auto"];
[defaults setValue:#"NO" forKey:#"is_sync_manual"];
[defaults synchronize];
when the user starts syncing out of impatience, check the defaults: if it is YES, do not do anything; otherwise, start syncing (and also set the flag, as above).
To redirect the request, you can create a shared instance and then invoke it again.
OR
A better way would be to create a singleton class.
Let that singleton class handle the sync operation, and as no more then one instance can be created, it will solve your problem.
+ (instancetype)sharedConnection {
static ConnectionClass *_sharedConnection = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_shared_sharedConnection = [[ConnectionClass alloc] init];
});
return _sharedConnection;
}

History of previously connected bluetooth devices using MCNearbyServiceBrowser

I'm using MCNearbyServiceBrowser to discover nearby bluetooth devices & inviting using invitePeer: toSession: withContext: timeout:.
Now the problem is that i want to keep history of all connected devices & re-connect them in future (manually by clicking them in UITableview) if the are nearby again. Is this possible? And is it compatible according to Apple's policy? Thanks.
UPDATE:
I already achieved the goal as "jamdaddy25" answered (before he answered :P) & it's working fine too.. But what if two devices have the same name? In that case it will be a problem of not showing one of the two or more devices in the list. And i don't want that. BTW thanks for response.
You could keep the peerID display name. This is a property on MCPeerID. When you have started browsing, either prior to user selecting a previously connected peer or starting up browsing upon coming to that screen, cycle through the nearby peers and see if u have a display name match. If so, you can systematically send an invite and reestablish the connection.
You could even do this so the only selectable previous connections are those which are currently nearby peers.
Update:
So to make sure that you don't have peer name collisions you need to make the names unique. The best way I know how to do this is to create a UUID based name. I save this inside of a simple object (UserPeerInfo below) and saving / load this to NSUserDefaults so this peer name will be used always for this peer
// Initialize with any stored data
if (!_userPeerInfo) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"userPeerInfo"]) {
NSData *userPeerInfoData = [defaults objectForKey:#"userPeerInfo"];
_userPeerInfo = (UserPeerInfo*)[NSKeyedUnarchiver unarchiveObjectWithData:userPeerInfoData];
} else
{
NSString *peerName = [[NSUUID UUID] UUIDString];
_userPeerInfo.peerName = peerName;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// Create an NSData representation
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:_userPeerInfo];
[defaults setObject:data forKey:#"userPeerInfo"];
[defaults synchronize];
}
}
Then when setting the name of your peer and initializing, use that peer name like normal
self.peerId = [[MCPeerID alloc] initWithDisplayName:self.userPeerInfo.peerName];
self.advertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:self.peerId discoveryInfo:info serviceType:kServiceType];
self.advertiser.delegate = self;
[self.advertiser startAdvertisingPeer];

How to keep information from reseting

I have a check box type application, kinda like notes on the iPhone. When the user closes the application and double tabs the home button and deletes the application from the background, the things they have typed also get deleted or reset. I was thinking of calling
[self saveContext];
But that didn't work. WHat should I be calling?
You can usual use something such as NSUserDefaults to save information. Or, you can go another route and save the information in a data structure and write that to a file. There are many easy ways of doing this. For instance, if you want to preserve a checked value, you could do the following:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:"checked"];
[defaults synchronize];
Very simple, and then can be queried across app exits, like so:
if( [defaults boolForKey:"checked"] ) {
//do stuff here
}

Check for first launch of my application

How would I check if it is the first launch of of my application using NSUserDefaults and running some code for the first time my app opens?
This should point you in the right direction:
static NSString* const hasRunAppOnceKey = #"hasRunAppOnceKey";
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
if ([defaults boolForKey:hasRunAppOnceKey] == NO)
{
// Some code you want to run on first use...
[defaults setBool:YES forKey:hasRunAppOnceKey];
}
The NSUserDefaults answer is the first thing that popped in my head, but upon reflection I will make another suggestion. A bit more work, but it's worth considering. The motive is: sometimes when troubleshooting an app, Apple recommends deleting that app's plist file. It's a fairly ubiquitous troubleshooting technique. I would recommend storing your boolean in your plist file instead of NSUserDefaults.
Disclaimer: I only do iOS development, so I'm not sure how NSUserDefaults and plists interact on the Mac, and I don't know what all is involved in getting your plist to live in ~/Library/Application\ Support/Preferences/com.mycompany.MyAppName.plist
Anyway, I imagine what this requires is having some code which can actually author a "fresh" plist (probably a copy from a template file in your bundle), and you app does this if it launches and does not see a plist. The default plist should not include the flag which lets your users skip the 'first time' code, but if they have opened the app before, and then delete the plist, they should get default behavior back.
This is an important behavior to support where possible, to aide our users if our app ever gives them trouble.
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"hasBeenLaunched"]) {
// Run code on the first launch only ...
[defaults setBool:YES forKey:#"hasBeenLaunched"];
}
You can use NSUserDefaults to save bools, integers, objects into the program and have them available whenever you open it. You can use 'boolForKey' to set a flag called "hasBeenLaunched". By default, this value will be NO when not set. Once you change it to YES, the code in the if condition will never be executed again.
In your main controller class, implement something like this:
static NSString * const MDFirstRunKey = #"MDFirstRun";
#implementation MDAppController
+ (void)initialize {
NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
[defaults setObject:[NSNumber numberWithBool:YES] forKey:MDFirstRunKey];
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
// the following if on Mac and is necessary:
[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:defaults];
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
BOOL firstRun = [[[NSUserDefaults standardUserDefaults]
objectForKey:MDFirstRunKey] boolValue];
if (firstRun) {
// do something
[[NSUserDefaults standardUserDefaults] setObject:
[NSNumber numberWithBool:NO] forKey:MDFirstRunKey];
} else {
// do something else
}
}
#end
The +initialize class method is called before an instance of the class it's found in is created; in other words, it is called very early on, and is a good place to set up your default values.
See Preferences and Settings Programming Guide: Registering Your App's Default Preferences for more info.