I'm making a game and when I close the app (close at multitask manager), all my data is gone! So, My question is very simple: How do I save the data?
Let's say you want to save score and level, which are both properties of an object called dataHolder.
DataHolder can be created as a singleton, so you don't have to worry too much about from where you access it (its sharedInstance actually):
It's code would look a bit like this:
DataHolder.h
#import <Foundation/Foundation.h>
#interface DataHolder : NSObject
+ (DataHolder *)sharedInstance;
#property (assign) int level;
#property (assign) int score;
-(void) saveData;
-(void) loadData;
#end
DataHolder.m
NSString * const kLevel = #"kLevel";
NSString * const kScore = #"kScore";
#implementation DataHolder
- (id) init
{
self = [super init];
if (self)
{
_level = 0;
_score = 0;
}
return self;
}
+ (DataHolder *)sharedInstance
{
static MDataHolder *_sharedInstance = nil;
static dispatch_once_t onceSecurePredicate;
dispatch_once(&onceSecurePredicate,^
{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
//in this example you are saving data to NSUserDefault's
//you could save it also to a file or to some more complex
//data structure: depends on what you need, really
-(void)saveData
{
[[NSUserDefaults standardUserDefaults]
setObject:[NSNumber numberWithInt:self.score] forKey:kScore];
[[NSUserDefaults standardUserDefaults]
setObject:[NSNumber numberWithInt:self.level] forKey:kLevel];
[[NSUserDefaults standardUserDefaults] synchronize];
}
-(void)loadData
{
if ([[NSUserDefaults standardUserDefaults] objectForKey:kScore])
{
self.score = [(NSNumber *)[[NSUserDefaults standardUserDefaults]
objectForKey:kScore] intValue];
self.level = [(NSNumber *)[[NSUserDefaults standardUserDefaults]
objectForKey:kLevel] intValue];
}
else
{
self.level = 0;
self.score = 0;
}
}
#end
Don't forget to #import "DataHolder.h" where you need it, or simply put it in ...-Prefix.pch.
You could perform actual loading and saving in appDelegate methods:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[[DataHolder sharedInstance] saveData];
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[[DataHolder sharedInstance] loadData];
}
You can access your score and level data from anywhere with [DataHolder sharedInstance].score and [DataHolder sharedInstance].level.
This might seem like an overkill for a simple task but it sure helps to keep things tidy and it can help you to avoid keeping all the data in appDelegate (which is usually the quick & dirty path to solution).
[NSUserDefaults standardUserDefaults] is good for small amounts of data like user settings and preferences. Typically you use this to enable users to save various bits of data that define global values such as character preferences, weapons preferences, whatever, etc.
For larger amounts of data like game level details or achievements or weapons inventory, etc. You will want to use something like Core Data. This is a more formal database that can be easily migrated as your data schema changes. See the docs here:
Core Data and Core Data Programming Guide
You can save data in CoreData, SqlLite or NSUserDefaults
Update
Realm is also an option and very easy to implement.
There are few ways to save data in ios.
UserDefaults - great way to save a small amount of data.
Keychain - safe location to safe high sensible data like login data and passwords.
Sqlite Database - If your application have a huge amount of structured data
CoreData - based on an object graph that describes the objects that should be saved
Saving Files - Of course you can also directly save all types of files to the file system. However, you can just access the file system within the app container due to security reasons.
Using Realm database - best alternative to Core Data and SQLite db
NSKeyedArchiver and NSKeyedUnarchiver - better way for small amount of objects. // i think so
ref - http://www.thomashanning.com/data-persistence-ios/
Swift 3
Use UserDefaults
UserDefaults.standard.set(„some value”, „key”)
let value = UserDefaults.standard.string(„key”)
You can even persist array using this
I recommend using Realm for more generalized solutions.
Related
I'm new for iOS, i'm using AF-Networking framework for fetching the web services and successfully getting the data and loading it to the UI elements now what's my issue is Application performance is slow and it's loading form the web service every time i want to cache the images and data locally and increase the performance of the application can anyone out there can help me with the proper solution.
Thanks in Advance
I think what you are talking about has nothing to do with NSURLCache but to save the previous network request data locally. Then next time before you send a network request you can read from local file first
There are many different ways of saving data locally like Core Data NSKeyedArchiver plist FMDB. Here is my way using NSKeyedArchiver.
(put the interface here you can read the implementation at this link https://github.com/dopcn/HotDaily/blob/master/HotDaily/HDCacheStore.m)
#interface HDCacheStore : NSObject
+ (HDCacheStore *)sharedStore;
#property (copy, nonatomic) NSArray *mainListCache;
#property (copy, nonatomic) NSArray *funinfoListCache;
- (BOOL)save;
#end
in XXXAppDelegate.m
- (void)applicationWillResignActive:(UIApplication *)application //Or some other place
{
if ([[HDCacheStore sharedStore] save]) {
NSLog(#"save success");
} else {
NSLog(#"save fail");
}
}
I think you have to use NSUserDefaults, follow the following
1: create NSArray of the data u need
2: Save into NSUserDefaults with a key
3: Now you are able to use that NSArray in every class of the application
4: if you want update data simply again save into NSUserDefaults with old key
So you don't need to download data every time, just download once and save into NSUserDefaults and use.
if there is problem in saving data in NSUserDefaults then look at code bellow
To store the information:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:arrayOfImage forKey:#"tableViewDataImage"];
[userDefaults setObject:arrayOfText forKey:#"tableViewDataText"];
To retrieve the information:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSArray *arrayOfImages = [userDefaults objectForKey:#"tableViewDataImage"];
NSArray *arrayOfText = [userDefaults objectForKey:#"tableViewDataText"];
// Use 'yourArray' to repopulate your UI
I'm learning Objective C & Xcode, by doing my first App.
First a user has to sign in. And then he can do several things, like joining a group or changing his data (username, email,...).
The Login is finished and it works fine.
To the question:
Is it possible to set a variable which I can reach from every View Controller?
I tried it with the segues, but I think it's very hard to define this in every View Controller.
I'm searching for a global variable which I can reach from everywhere in the App, is this possible?
Or is there an other method to solve this?
Thank you for the help!
Emanuel
I would personally advise you to use the Singleton pattern.
I would not recommend putting everything into AppDelegate, this class is not meant for that.
Instead, what you can do is create a dedicated class (with a name like "ApplicationState", or whatever suits you), and put the properties you need in its header file, and having your singleton management code in the .m (and the prototype in the header)
If you need the singleton management code, here it is:
+ (ApplicationState*)sharedInstance
{
static ApplicationState* sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
Then, if you have in the header:
#property (nonatomic, strong) NSObject* object;
+ (ApplicationState*)sharedInstance;
You will be able to get this variable from anywhere by including the ApplicationState header file, and call:
[[ApplicationState sharedInstance] object];
You can use NSUserdefault if its simple .
Save:
[[NSUserDefaults standardUserDefaults]setObject:username forKey:#"userName"];
[[NSUserDefaults standardUserDefaults] synchronize];
NSLog(#"username saved = %#", username);
Read:
NSString *username = [[NSUserDefaults standardUserDefaults] objectForKey:#"userName"];
NSLog(#"username retrieved = %#", username);
You can use AppDelegate as well.
Define a property in your AppDelegate.h,
#property (nonatomic, strong) NSString *userName;
And then in your view controller, after importing AppDelegate.h,
AppDelegate *appDel = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSLog(#"%#", appDel.userName);
You can put them in plist or you can create Objective-C class initialize your variables within and import header in .pch file, then this class with data will available in every ViewController
I need to share an NSMutabaleArray between multiple ViewControllers, so I created a singleton. But when I re-launch my app, the array seems to clear itself which causes problems within my app. This is my singleton:
.h:
#import <Foundation/Foundation.h>
#interface playlistArray : NSObject {
NSMutableArray *playlistSongsArray;
}
#property (nonatomic, retain) NSMutableArray *playlistSongsArray;
+ (id)sharedArray;
#end
.m
#import "playlistArray.h"
#implementation playlistArray
#synthesize playlistSongsArray;
#pragma mark Singleton Methods
+ (id)sharedArray {
static playlistArray *sharedArray = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedArray = [[self alloc] init];
});
return sharedArray;
}
- (id)init {
if (self = [super init]) {
self.playlistSongsArray = [[NSMutableArray alloc] init];
}
return self;
}
- (void)dealloc {
// Should never be called, but just here for clarity really.
}
#end
I thought about storing it in NSUserDefaults - is this the correct route? If so, how would I do this?
Any help would be much appreciated, thanks!
I am not sure creating a separate singleton class for just passing an array to different view controllers is a great idea from code structure point of view. Much more cleaner approach would be to pass playlistSongsArray directly whenever you create a new instance of a view controller that may need it.
However, if you still want to use singleton in your implementation for some reason, I'd change playlistArray class to something like PlaylistManager (notice that the common convention is to capitalise first letters of each word in class names) — by doing this, you don't constraint yourself with array-only implementation and you can use it to share other playlist information between your view controllers.
As for persistency between app launches, it really depends on what kind of data you store in your array. For example, you can use NSUserDefaults if your array stores relatively small number of NSStrings (or other <NSCoding>-compliant classes). Other most common options are NSKeyedArchiver and Core Data. You can learn more about data persistency on iOS from Apple documentation or great online tutorials like this one on NSHipster.
I thought about storing it in NSUserDefaults - is this the correct route?
NSUserDefaults is certainly an option you have.
You can add the following two methods into your class and call it when appropriate (i.e., savePlayList when you modify the array;loadPlaylist` in the singleton init method):
- (void)savePlaylist {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:self.playlistSongsArray forKey:#"playlistSongs"];
[defaults synchronize];
}
- (void)loadPlaylist {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
self.playlistSongsArray = [[defaults arrayForKey:#"playlistSongs"] mutableCopy];
}
Have a look at this post for a discussion about different approaches you have.
You get singleton array blank when you relaunch the app because all memory allocated to the array is removed. These singleton classes are allocated once during the lifecycle of the app.
So the best answer to fetch the data every time you open the app is to save the data into the disk. To do so there are few ways
Database using Sqlite
plist
NSUSerDefaults
You can go for userdefaults or plist if you want to save the app.
Below is the example of saving the array into userdefault
Saving the array into userdefaults:
// Get the standardUserDefaults object, store your UITableView data array against a key, synchronize the defaults
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:array forKey:#"singletonArray"];
[userDefaults synchronize];
Retrieving the array:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSArray *singletonArray = [userDefaults objectForKey:#"singletonArray"];
Hope it will help you. Happy Coding :)
I want to create a user that the app fetches from disk every time the app is opened, and written every time it is closed. I want the data from the user, such as NSString name, along with some other variables, to be accessible from any other point in the app.
There will only be one user so it is kind of a "global" variable. Also, if the user class includes pointers to data structures like NSMutableDictionary, or another instance of NSObject, are there any precautions I need to take?
I want to learn the best way to implement this, any suggestions?
NSUserDefaults is your friend. It meets your requirements to be accessible from any other point in the app.
1) Create a class to represent your user
2) Have that class implement the NSCoding protocol
3) Use NSFileManager to create or open a file as necessary
4) Use that file to restore / store the user class. (Its easy using the NSCoding protocol)
5) Make the user class owned by your model which exposes the data in the user class, or make the user class a singleton (The first is much better).
You should be able to find examples / tutorials if you search how to store stuff using NSCoding
You can create an UserObject extended by NSObject. Use a Singleton Pattern so you always have the same object inside every class.
Then you could just save every variable inside NSUserDefaults by using a specific key.
If you want to save the whole object inside NSUserDefaults you need to include the NSCoding in the interface
#interfaces User : NSObject <NSCoding>
and use the methods initWithCode: and encodeWithCoder:
- (id)initWithCoder:(NSCoder *)aDecoder{
self = [super init];
if( self ){
strName = [aDecoder decodeObjectForKey:#"name"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:strName forKey:#"name"];
}
for un / archiving do it this way:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:userObject];
UserObject *user = [NSKeyedUnarchiver unarchiveObjectWithData:data];
if you want some more information you can read all about it here.
Use keychain in case there is private user data to store.
In most cases Martin's answer is the best way.
NSUserDefaults is a great option. You can access it from every point on your app, save and extract data anytime and store most of the things you'll probably need.
To save:
NSUserDefaults *userDef = [NSUserDefaults standardUserDefaults];
[userDef setObject:userDictionary forKey:#"userData"];
[userDef synchronize];
To Extract:
NSUserDefaults *userDef = [NSUserDefaults standardUserDefaults];
NSDictionary *userDictionary = [userDef objectForKey:#"userData"];
You can save your data on your AppDelegate's
- (void)applicationWillTerminate:(UIApplication *)application;
And Extract on:
- (void)applicationWillEnterForeground:(UIApplication *)application;
Here is my mainAppDelegate.h:
#import <UIKit/UIKit.h>
#interface mainAppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property NSMutableArray *toDoItems;
#end
and my mainAppDelegate.m:
#import "mainAppDelegate.h"
#implementation mainAppDelegate
- (void)applicationWillTerminate:(UIApplication *)application
{
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaults setObject:self.toDoItems forKey:#"toDoItems"];
[defaults synchronize];
}
#end
I have another file, XYZToDoListViewController.m with the code:
#import "XYZToDoListViewController.h"
#import "XYZToDoItem.h"
#import "XYZAddItemViewController.h"
#interface XYZToDoListViewController ()
#property NSMutableArray *toDoItems;
#end
#implementation XYZToDoListViewController
- (IBAction)unwindToList:(UIStoryboardSegue *)segue
{
XYZAddItemViewController *source = [segue sourceViewController];
XYZToDoItem *item = source.toDoItem;
if (item != nil) {
[self.toDoItems addObject:item];
[self.tableView reloadData];
}
}
- (IBAction)clearData:(UIBarButtonItem *)sender;
{
[self.toDoItems removeAllObjects];
[self.tableView reloadData];
}
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
}
return self;
}
NSUserDefaults *defaults= [NSUserDefaults standardUserDefaults];
if([[[defaults dictionaryRepresentation] allKeys] containsObject:#"toDoItems"]){
NSLog(#"toDoItems found");
self.toDoItems = [[NSMutableArray alloc]initWithArray:[[NSUserDefaults standardUserDefaults]objectForKey:#"toDoItems"]];
} else {
self.toDoItems = [[NSMutableArray alloc] init];
}
self.navigationController.view.backgroundColor =
[UIColor colorWithPatternImage:[UIImage imageNamed:#"bg_full.png"]];
self.tableView.backgroundColor = [UIColor clearColor];
self.tableView.contentInset = UIEdgeInsetsMake(-35, 0, -35, 0);
}
#end
This is at least what I think is relevant. My basic framework is something I followed from this tutorial.
What am I doing wrong? When I add items into my to-do list, terminate the application, then relaunch the app the data i previously entered does not display. There are no errors or warnings on the project, or crashes.
applicationWillTerminate: isn't called I presume, put that userdefault synchronize code somewhere else.
By the way, when you restart debugger, the process is just killed, not "terminated".
That is why I usually call synchronize at every change of data, to make sure data is saved in case of a crash, or simply when I restart debugger.
On a totally different side note, I'd like to point out to you that you can change this
if([[[defaults dictionaryRepresentation] allKeys] containsObject:#"toDoItems"]){
NSLog(#"toDoItems found");
self.toDoItems = [[NSMutableArray alloc]initWithArray:[[NSUserDefaults standardUserDefaults]objectForKey:#"toDoItems"]];
} else {
self.toDoItems = [[NSMutableArray alloc] init];
}
To that
self.toDoItems = [[NSMutableArray alloc]initWithArray:[[NSUserDefaults standardUserDefaults]objectForKey:#"toDoItems"]];
Because if you try to get an object that is not set from user defaults (or any dictionary), you get nil back. And if you init your mutable array with a nil array you will have a valid, yet empty, mutable array.
Since it didn't fit as a comment I wrote it as an answer, though of course it has nothing to do with your original problem
[defaults setObject:self.toDoItems forKey:#"toDoItems"];
[[NSUserDefaults standardUserDefaults]objectForKey:#"loadItems"]
The 2 don't have the same key. You set #"toDoItems" and try to get #"loadItems".
You are not persisting the data. Right now, when you create a item, it lives in memory, but it is never written to the disk, so when the app is terminated, the memory that contained the item is released. You need to write the items to the disk so they can be used after the app is terminated. Either you can archive the items, or you can use Core Data. Don't use NSUserDefaults, which is for user setting.Here is a tutorial for archiving, you can find the Core Data guide on the Apple developer website.
You'll have an exceptionally difficult time trying to simulate applicationWillTerminate being called.
I suggest you save your data when it changes (preferred) or at least on something a bit more predictable applicationWillEnterForeground: or applicationWillResignActive:.
- (void) applicationWillResignActive:(UIApplication *)application
{
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaults setObject:self.toDoItems forKey:#"toDoItems"];
[defaults synchronize];
}
As Emilie Lessard pointed out, you must use the same key to get the right data.
[[NSUserDefaults standardUserDefaults]objectForKey:#"toDoItems"];
I suppose you don't save the list on the mainAppDelegate class. So, you need to save your data on the class where the list is being formed and delete the invocation your mainAppDelegate. Maybe you're saving a nil value when your app terminate.
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaults setObject:self.toDoItems forKey:#"toDoItems"];
[defaults synchronize];
And later you need to invoke it with the same key #"toDoItems".