Storing custom objects as NSDictionary vs NSData - ios

I was going through some tutorials regarding storing custom objects in NSUserDefaults. I can't figure out which is the better approach and/or benefits regarding storing your model after converting it back to NSDictionary and storing the dictionary vs using NSKeyedArchiver to store the object in NSUserDefaults. As per my understanding you will have to set the encodeWithCoderand initWithCoder methods which will enable you set the values for various keys and basically convert everything to NSData. Converting the model back to NSDictionary will also follow the same steps.
So what are the benefits of using one approach over the other or if one approach may cause something to break anywhere?
Using NSKeyedArchiver will do something like:
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.test forKey:#"string"];
[encoder encodeObject:self.surname forKey:#"surname"];
}
And converting model back to NSDictioanry:
-(NSDictionary *)dictionaryWithModel:(Model *)model{
NSDictionary *dictionary = #{
#"dict":[model.innerModel dictionaryWithModel:model.innerModel],
#"string":self.test
};
return dictionary;
}
Basically which of the would be better when storing object in NSUserDefaults?
Edit: So according to the doc linked in the answer, we should use NSDictionary to store objects in NSUserDefaults (better performance), then why does apple recommend using NSKeyedArchiver?

Here is my answer on another similar question.
You can set object like this:
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:savingbean];
[currentDefaults setObject:data forKey:#"DATA"];
[currentDefaults synchronize];
and for get object like this:
NSData *data = [currentDefaults objectForKey:#"DATA"];
SavingBean *token = [NSKeyedUnarchiver unarchiveObjectWithData:data];
For Custom class you have to edit this methods in you bean class:
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:self.userName==nil?#"":self.userName forKey: #"userName"];
[encoder encodeObject:self.passWord==nil?#"":self.passWord forKey: #"passWord"];
}
-(id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if(self)
{
self.userName = [decoder decodeObjectForKey: #"userName"];
self.passWord = [decoder decodeObjectForKey: #"passWord"];
}
return self;
}
Here is most voted similar answer. You can also get good stuff from there.
You can also use JSON Accelerator to create bean class. This is very simple and powerful tool.
EDIT:
The NSUserDefaults class provides convenience methods for accessing
common types such as floats, doubles, integers, Booleans, and URLs. A
default object must be a property list, that is, an instance of (or
for collections a combination of instances of): NSData, NSString,
NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any
other type of object, you should typically archive it to create an
instance of NSData.
Values returned from NSUserDefaults are immutable, even if you set a
mutable object as the value. For example, if you set a mutable string
as the value for "MyStringDefault", the string you later retrieve
using stringForKey: will be immutable.
Note: The user defaults system, which you programmatically access
through the NSUserDefaults class, uses property lists to store objects
representing user preferences. This limitation would seem to exclude
many kinds of objects, such as NSColor and NSFont objects, from the
user default system. But if objects conform to the NSCoding protocol
they can be archived to NSData objects, which are property
list–compatible objects. For information on how to do this, see
““Storing NSColor in User Defaults”“; although this article focuses on
NSColor objects, the procedure can be applied to any object that can
be archived.
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsuserdefaults_Class/Reference/Reference.html
EDIT:
When defining your app’s preferences, it is better to use simple
values and data types whenever possible. The preferences system is
built around property-list data types such as strings, numbers, and
dates. Although you can use an NSData object to store arbitrary
objects in preferences, doing so is not recommended in most cases.
Storing objects persistently means that your app has to decode that
object at some point. In the case of preferences, a stored object
means decoding the object every time you access the preference. It
also means that a newer version of your app has to ensure that it is
able to decode objects created and written to disk using an earlier
version of your app, which is potentially error prone.
A better approach for preferences is to store simple strings and
values and use them to create the objects your app needs. Storing
simple values means that your app can always access the value. The
only thing that changes from release to release is the interpretation
of the simple value and the objects your app creates in response.
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/UserDefaults/AboutPreferenceDomains/AboutPreferenceDomains.html#//apple_ref/doc/uid/10000059i-CH2-SW2

Related

Mutable Objects inside NSUserDefaults

I created a simple database and put in NSUserDefaults. My database is NSMutableArray which has dictionaries and arrays inside in it. When I create NSMutableArray from NSUSerDefaults I can't add any objects to my mutable objects inside my NSMutableArray. Here is my code:
NSMutableArray *arrayOne = [NSMutableArray arrayWithContentsOfFile:[self createEditableCopyOfIfNeededWithFileName:#"Form.plist"]];
NSUserDefaults *ayarlar = [NSUserDefaults standardUserDefaults];
[ayarlar setObject:arrayOne forKey:#"form"];
NSMutableArray *arrayTwo = [NSMutableArray arrayWithArray:[[ayarlar objectForKey:#"form"] mutableCopy]];
[[[arrayTwo objectAtIndex:0] objectForKey:#"itemlar"] addObject:#"hop"];
And here is the error:
'NSInternalInconsistencyException', reason: '-[__NSCFArray insertObject:atIndex:]: mutating method sent to immutable object'
How can I make this work? Thank you everyone.
NSUserDefaults is not the right place to store your data. First of all it should only be used for very small amounts of data, such as settings. And secondly it always returns immutable objects, even if you set mutable ones. Making a mutable copy of your first array doesn’t help because only the array will be mutable. Everything that is inside that array isn’t touched by the mutableCopy method and stay immutable.
You should use the NSPropertyListSerialization class to read and write your data from a file. On reading you can pass options controlling the mutability of the read objects. There you will want to pass NSPropertyListMutableContainers or NSPropertyListMutableContainersAndLeaves.
With the first all your containers (arrays and dictionaries that is) will be mutable. With the latter also the leaves (that is NSString and NSData objects) will be mutable.
Depending on how big your data set can get you probably should use a real database or Core Data instead.
NSUserDefaults never returns mutable objects.
Your code is performing every way of creating a mutable array you can think of (i.e. you're creating a mutable copy of something you just created a mutable copy of), but, you're only dealing with the root container item - not the inner / leaf items. So, when you do objectForKey:#"itemlar"] on your mutable array, you're getting an immutable object back.
To make it work, you'll need to write your own method that iterates and recurses through the array creating mutable copies at all levels.
Alternatively, you could look at a 3rd party option like this which digs under the hood of NSUserDefaults to generate mutable containers.
NSUserDefaults, and property lists in general, do not record mutability. When an object is re-created from the file it can be constructed either as a mutable or immutable object (for types which have the option, such as arrays). Unfortunately NSUserDefaults doesn't give you an API call to obtain an immutable object directly.
Two options you have are (a) create a mutable copy of the object returned by NSUserDefaults or (b) store the object yourself as a property list in a separate file - that way you can read it back as mutable directly.
For (b) read Apple's docs - it shows how mutability is handled.
You can directly do by using method insertObject.
In place of
[[[arrayTwo objectAtIndex:0] objectForKey:#"itemlar"] addObject:#"hop"];
use this,
[arrayTwo insertObject:#"hop" atIndex:0];
This will work for as i have tested it's also working finr after that you can make it as immutable object as NSARRAY and save it to NSUSERDEFAULTS.

Can NSUserDefaults hold a NSArray with custom objects?

The title pretty much explains it. Do I need to serialize the objects first, or is it possible?
You have to code/decode the objects in your object (which is in your array) with and archive the array to NSData.
Just add
<NSCoding>
to the Class of your Objects (in your array) and follow the warnings of your compiler :D
Then archive your array like this:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:yourArray];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:data forKey:#"yourKey"];
NSArray *array= [NSKeyedUnarchiver unarchiveObjectWithData:[defaults objectForKey:#"yourKey"];
Check this out http://soff.es/archiving-objective-c-objects-with-nscoding
You need to serialize it.
From apple documentation for NSUSerDefaults:
The NSUserDefaults class provides convenience methods for accessing common types such as floats, doubles, integers, Booleans, and URLs. A default object must be a property list, that is, an instance of (or for collections a combination of instances of): NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any other type of object, you should typically archive it to create an instance of NSData. For more details, see Preferences and Settings Programming Guide.
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/Reference/Reference.html
Yes you can do it.
Check for your answer here:
How to set an NSArray in NSUserDefaults?
yes, it is possible,just you have to take care of NULL values while storing & retrieving it,

Create a copy of a NSManagedObject

I need to temporarily store the content of a NSManagedObject into a dictionary. Because core data has its own memory management procedures, I don't want to keep any strong pointers to the NSManagedObject's fields, only the values are of interest at this point (values are passed between view controllers, the MOCs are different). I can't create weak pointers either because I want to control when the memory reclaim is done.
I tried a few things, all failed or did not fit the purpose.
a duplicate [[myNSMO alloc] initWithEntity:[NSEntityDescription entityForName:entity inManagedObjectContext:myNSMO.managedObjectContext] insertIntoManagedObjectContext:nil];
It's working, but does not fit into my app design (without getting into details)
generate a NSDictionary from the NSManagedObject, using [myNSMO dictionaryWithValuesForKeys:<#(NSArray *)#>]. That's not ok because it returns a dictionary with the addresses of the NSManagedObject fields.
create a NSDictionary populating each key-value using a copyWithZone, like this
[myDictionary setObject:[myNSMO.field copyWithZone:nil] forKey:#"Key"];
Doesn't work either, I still get the field address...
Manually enter each field with
[myDictionary setObject:[NSString stringWithFormat:#"%#",myNSMO.field ] forKey:#"Key"];
It's fine this time, I do get new memory allocation. But that's highly time consuming to code this manually...
Any chance that someone found clever way to do that? the reason option 1) did not work is because I use the dictionary as a queue. I first store a copy of the object, then pop the entry out when required. A copy of that particular dictionary entry is then returned to the asking method. The problem is that I can't create a copy of an NSManagedObject that was created using [[...] insertIntoManagedObjectContext:nil];
Any solutions?
It's safe to keep strong pointers to the fields of a managed object in most senses — relationships are special but the actual Foundation objects of dates, strings and numbers are ordinary objects that'll stay in memory if you have a strong reference.
That being said, to create a dictionary copy containing all the properties of an entity you could do something like:
NSArray *properties = [[object entity] properties];
NSMutableDictionary *dictionaryRepresentation = [NSMutableDictionary dictionary];
for(NSAttributeDescription *attribute in properties)
{
// we want only actual attributes, not relationships
// or fetched properties
if([attribute isKindOfClass:[NSAttributeDescription class]])
{
[dictionaryRepresentation
setObject:[object valueForKey:attribute.name]
forKey:attribute.name];
}
}
So you're using the fact that managed objects expose a description of their entities which includes a list of properties, whittling those properties down to just the attributes, then using key-value coding to fetch the current value of each property and finally inserting it into the dictionary.
If for some reason you did want copies of the properties — though, as I say, there's absolutely no reason to do so — you'd copy (and autorelease if you're not using ARC) each property when inserting it into the dictionary.

Store an array of UIViews in NSUserDefaults

I'm trying to add an array of uiviews to NSDefault but it doesn't seem to be keep the array. Does any one know why? I also tried to store each view in nsvalue before storing it in nsdefault which still didn't work.
NSArray *arr = [[NSArray alloc] initWithObjects:[NSValue valueWithNonretainedObject:myView], nil]];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:arr forKey:#"myKey"];
NSArray *resultArray = [defaults objectForKey:#"myKey"];
and resultArray is nil!
Thanks
the reason why I'm trying to do this is because these are the header views of my uitableview. Since it takes time to create them I wanted to create them only once and store them for future access.
From the docs for NSUserDefaults:
The NSUserDefaults class provides convenience methods for accessing common types such as floats, doubles, integers, Booleans, and URLs. A default object must be a property list, that is, an instance of (or for collections a combination of instances of): NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any other type of object, you should typically archive it to create an instance of NSData. For more details, see Preferences and Settings Programming Guide.
If you want to put a UIView (why?) in NSUserDefaults, you need to archive it first into an NSData object.
But you need to ask yourself why you want to put a view in NSUserDefaults. You should only be putting bits of data in NSUserDefaults. Views display data. It's easy to redisplay a view once you have the data back. Consider just putting some needed data in NSUserDefaults.
Are you sure you want to do that? It is definitely better to store an array of models to the data base or some file and recreate views from them when needed.
A ha! You are not the first person to face this issue. I've not had this type of issue myself but, in the link below, is a blog with code that allows you to cache and re-use your views. Then you would only need to re-create the views when you launch. Example code:
Cache UIViews for re-use in tableview

Best way to save data to iOS?

In my application (iOS 5) I want to save data - I want to save debts.
So its:
plus or minus money
the amount of money
and the name who has the debts (or the name where you have the debts)
But I don't how to save the data (NSUserdefaults,Core data, SQLLite)
Maybe you can tell me the best way to save them?
The easiest way to store small amount of data on your device is to use NSUserDefaults. But only property lists could be saved in this way. A property list is a combination of objects of 6 types, NSNumber, NSString, NSArray, NSDictionary, NSDate, NSData.
In your case it's easy to do. For example, to save a new debt record you can use following method:
#define DEBTS_LIST_KEY #"listOfAllDebts"
#define DEBTOR_NAME_KEY #"debtorName"
#define DEBT_AMOUNT_KEY #"amountOfDebt"
-(void) saveDebt:(CGFloat) debtAmount forName:(NSString *) debtorName
{
// pointer to standart user defaults
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
// the mutalbe array of all debts
NSMutableArray * alldebtRecords = [[defaults objectForKey:DEBTS_LIST_KEY] mutableCopy];
// create new record
// to save CGFloat you need to wrap it into NSNumber
NSNumber * amount = [NSNumber numberWithFloat:debtAmount];
NSDictionary * newRecord = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:amount,debtorName, nil] forKeys:[NSArray arrayWithObjects:DEBT_AMOUNT_KEY, DEBTOR_NAME_KEY, nil]];
[alldebtRecords addObject:newRecord];
[defaults setObject:alldebtRecords forKey:DEBTS_LIST_KEY];
// do not forget to save changes
[defaults synchronize];
}
To readList of debts you have read something similar.
But I recommend you to use core data. It's more flexible and you won't have to write all this code to manage your data (to edit existed records, or to delete them). You will be able to extend your model much easier, for example, when you want to save the date of the debt. This is the link to a good tutorial
If the quantity of records is user-defined, and will grow with app use, I suggest Core Data, which can be backed by SQLite. If you are working in a modern Xcode (i.e. Xcode 4), creating models is easy and graphical. If you have ever worked with ORM frameworks before, the interface for querying, etc. should be easy to grasp.
Search around for some tutorials, but be specific about finding tutorials that match your version of Xcode, as Core Data development has been changing a lot lately.
Good and easy way is to create your own objects and serialize them using NSCodying and NSCopying
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Protocols/NSCopying_Protocol/Reference/Reference.html
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Protocols/NSCoding_Protocol/Reference/Reference.html

Resources