How to store a NSManagedObjectID persistently? - ios

To avoid being an XY problem, here's some background:
My app allows users to create and save a lot of settings kinda like the Xcode's fonts and colors chooser:
This is because there are a lot of things that users can set. It would be easier to just tap on a saved setting instead of setting all those things again.
I used Core Data to store the settings the user saved. Each setting the user created is an instance of an NSManagedObject subclass. And now I need to store the selected setting persistently so that the user will have the same setting selected as before when the app reopens.
My first thought was to store the NSManagedObject subclass instance in NSUserDefaults. But according to the docs, I can't store it unless I convert it to NSData:
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.
Then I tried storing the objectID of the NSManagedObject subclass instance. I see that there is a URIRepresentation() method that returns an NSURL. So I thought this would work:
NSData(contentsOfURL: selectedOption!.objectID.URIRepresentation())
But the initializer failed somehow.
Now I realize that this is a stupid idea because even if I can convert it to NSData, I can't convert NSData back to NSManagedObjectID!
According to this question, the OP seems to be able to store the object id:
I store the selected theme objectID in NSUserDefaults so that when the app restarts, the selected theme will still be intact.
How can I do that?

NSUserDefaults has "convenience methods"
public func setURL(url: NSURL?, forKey defaultName: String)
public func URLForKey(defaultName: String) -> NSURL?
which allow to store and retrieve a NSURL like the one obtained
by URIRepresentation(). The conversion to and from NSData
is handled transparently. From the documentation:
When an NSURL is stored using -[NSUserDefaults setURL:forKey:], some adjustments are made:
Any non-file URL is written by calling +[NSKeyedArchiver archivedDataWithRootObject:] using the NSURL instance as the root
object.
...
When an NSURL is read using -[NSUserDefaults URLForKey:], the following logic is used:
If the value for the key is an NSData, the NSData is used as the argument to +[NSKeyedUnarchiver unarchiveObjectWithData:]. If the NSData can be unarchived as an NSURL, the NSURL is returned otherwise nil is returned.
...
So saving the managed object ID is simply done as
NSUserDefaults.standardUserDefaults().setURL(object.objectID.URIRepresentation(),
forKey: "selected")
and retrieving the object ID and the object for example like this:
if let url = NSUserDefaults.standardUserDefaults().URLForKey("selected"),
let oid = context.persistentStoreCoordinator!.managedObjectIDForURIRepresentation(url),
let object = try? context.existingObjectWithID(oid) {
print(object)
// ...
}
For alternative approaches of saving the selected settings, see the above comment.

Related

Is it possible to save an NSManagedObject in NSUserDefaults?

I've having difficulty saving an NSManagedObject in userDefaults and I'd like to know a) should I be trying to do this or is this not an appropriate approach or b) if it is an appropriate approach, how can I get it to work?
I'm writing my app in Swift 2.3 and it has a few user default options, one of which is for a default "lift" (as in weightlifting, e.g. 'bench press', 'clean and jerk', 'incline bench press'). I'm actually converting them from an enum to a Core Data entity because every lift event that the user will be able to keep track of will be one of the available lifts types (for which I'll establish the appropriate relationship).
Here's the extension with the properties:
extension Lift {
#NSManaged var liftName: String
#NSManaged var type: NSSet
}
and the Lift entity with the things Xcode is complaining about:
class Lift: NSManagedObject, NSCoding {
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(liftName, forKey: "liftName")
} // Super.init isn't called on all paths before returning from initializer
required init(coder aDecoder: NSCoder) {
// Initialization of immutable variable was never used, etc...
let liftName = aDecoder.decodeObjectForKey("liftName") as! String
}
}
I've dealt with these types of errors before so my real concern is whether or not I'm headed down the wrong path.
I've read numerous threads tonight which have taught me that I'll need to encode an object (but not specifically an NSManagedObject) to save it then unencoded it when retrieving it and that my class must conform to NSCoding and what that protocol requires. But then I've seen threads that say NSManagedObjects should NOT be stored in userDefaults, but I don't know if that's true.
I've spent a few hours on this so before I go further, can/should this be done?
No, you should not store an NSManagedObject in NSUserDefaults. Core Data is an object persistence technology, so it doesn't make sense to try and persist an NSManagedObject in some other format.
There are few alternatives you can use:
You could simply store the name of the lift in your user defaults and then query for this to get the object back from Core Data. This may not work for you if lift names aren't unique
You can add an identifier attribute to your Lift entity and store something like a UUID string in that attribute; You can then store the same string in UserDefaults. This will ensure one object is selected uniquely.
You can add a new boolean attribute to your Lift entity default and again use a query to retrieve it; You would need to ensure you only set the value to true on one lift at a time.
You can use managedObject.objectId.uriRepresentation to get a URL that you can store as a string and then use to retrieve the object later. This is probably the most complex solution and I would suggest you try one of the other options.
No, You cannot do this. You cannot save Core Data objects in User Defaults. It can only save in its DB.
This two are totally two different things. NSUserDefault stores the light pieces of data where NSManagedObject stores the light or heavy amount of data and is very fast than NSUserDefault for storing and retrieving purposes.
NSManagedObject -> NSManagedObject link to coredata.
You should it to store a large list of elements. As far your last question, there is nothing preventing you from using both Core Data and a backend to store your data. In fact, there are frameworks out there to facilitate exactly this.
NSUserDefaults -> NSUserDefaults is a class that allows simple storage of different data types. It is ideal for small bits of information you need to persist between app launches or device restarts. NSUserDefaults is not sufficient and reliable to store and query the huge amount of data. It's suggestable if you'll have a backend (database on the server) to store events and their invitees to persist consistency of user's information (if user logged in back to your app from other app supportive device then he'll get all information he stored).
NSUserDefaults supports the following data types:
NSString, NSNumber, NSDate, NSArray, NSDictionary and NSData
Hope it will help you.
Storable Types in NSUserDefaults.
The NSUserDefaults class acts very much like something called a Property List (aka plist). It may be just a fancy interface for a plist, or it may be more, I’m not entirely sure. Nonetheless, plists are limited in what kind of objects they can store. The six types plists can store are:
NSData
NSString
NSNumber
NSDate
NSArray
NSDictionary
So,you need to use in NSKeyedArchiver.
let ArchvieArr = NSMutableArray()
ArchvieArr.addObject(NSKeyedArchiver.archivedDataWithRootObject(LiftObj)).
NSUserDefaults.standardUserDefaults().setObject(ArchvieArr, forKey: "savedArray")

Saving custom object to NSUserDefaults - can't set encoder and decoder methods

How would you be able to save a custom object to NSUserDefaults??
NSUserDefaults.standardUserDefaults().setObject(obj, forKey: "object")
NSUserDefaults.standardUserDefaults().synchronize()
The object is of the SPTAuthViewController class in the Spotify iOS SDK, so I can't edit it to add encoder and decoder methods. How can I encode the object into an NSData object to store it in NSUserDefaults?
You can't store custom objects into user defaults. You can only store "property list objects" (NSString, NSData, NSDate, NSNumber, NSArray, or NSDictionary objects).
In order to save a custom object to user defaults you have to convert it to one of those types.
You can make your custom object conform to the NSCoding protocol. To do that you have to implement the init(coder:) and encode(coder:) methods. (I believe that's the swift form of the method names - I've been working in Objective-C lately so my Swift is getting rusty.) How you implement those methods depends on your class. Typically you make multiple calls to encodeObject:forKey: to encode your object's properties.
Once your custom object conforms to NSCoding you can convert it to NSData using the NSKeyedArchiver method archivedDataWithRootObject(). You then convert it from NSData back to the original object using the NSKeyedUnarchiver method unarchiveObjectWithData().
EDIT:
I just realized that you were trying to save a view controller object (SPTAuthViewController to user defaults. You should not serialize and save view controller objects. Controller objects should not be used to save application data.

Save a function to NSUserDefaults in Swift

So, I was wondering if it is possible to save a function to user defaults using Swift. For a string, for example, I would do this:
var inputData = "Hi"
NSUserDefaults.standardUserDefaults.setValueForKey(inputData, forKey: "data")
Then to save it do a variable, I would say:
var gotData = NSUserDefaults.standardUserDefaults.valueForKey("data")!
Now, my question is, if it is possible to save a function to user defaults. For example:
var inputData = func helloWorld(){println("Hello World!")}
Is it possible to then call that function that was saved to user defaults by doing something similar to .valueForKey ?
Any help is valued! Thanks a lot in advance!
You cannot save a function to NSUserDefaults. From the NSUserDefaults Class Reference:
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.
A function is not any of the supported types, and as far as I know you cannot archive a function either.

Is NSUserDefaults the right choice for this app idea in swift

I have a super simple app idea that I'm building out in order to get more practices at using Swift. I just want to ask if using NSUserDefaults is the most appropriate choice. All I will have is list of names with a number associated with each name that I will update (the number) from time to time. I was going to use a NSDictionary for the data.
I think Core Data is over kill for something like this.
I just wanted to get a 2nd opinion on my idea on how to save the data for this simple app.
Thanks
You can use NSUserDefault. But If want to store an array or dictionary you have to do some extra work. The documentation says:
The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects, their contents must be property list objects.
You can convert your NSDictionary to NSData, then save it to NSUserDefault.
// Convert the dictionary and store
var data = NSKeyedArchiver.archivedDataWithRootObject(yourDict)
NSUserDefaults.standardUserDefaults().setObject(data, forKey: yourKey)
// Retrieving the dictionary
var savedData = NSUserDefaults.standardUserDefaults().dataForKey(yourKey)
var dict = NSKeyedUnarchiver.unarchiveObjectWithData(savedData)
Hope this helps.. :)
If it's just one NSDictionary that should be fine. NSUserDefaults is good for small things like that.
NSUserDefaults.standardUserDefaults().setObject(**YOUR DICTIONARY**, forKey: **YOUR KEY**)

Add NSDictionary to NSManagedObject Category

I would like to add a NSDictionary into a NSManagedObject Category class (or the NSManagedObject class itself)
When I do this, and I try to access the property, an exception is thrown.
Is this actually possible? I can't add this property as transient in the model because there is no NSDictionary Data Type, of course.
Thanks!
You don't say how you have currently created the property or what the exception is, but from the description you give it sounds like you should be setting the attribute in the Core Data model to be transformable. Setting it to be transformable will cause the NSDictionary to be archived (and unarchived) as you use it using the standard NSCoding protocol. Be sure that everything you put into the dictionary supports the NSCoding protocol so that it is properly archived and restored.
Using transformable is the way. Below are few more insights on the transformable property.
The Transformable data type is a special data type that allows us to
create attributes based on an Objective-C class (custom objects). This
data type is heavily used for storing instances of UIImage, UIColor,
and so on. As the information stored in the persistent store has to be
in the form of NSData instance, while using Transformable data type,
we need to create Value Transformers to convert the custom object
(information in attribute of Transformable data type) into an instance
of NSData (before storing in the persistent store) and to convert the
instance of NSData back to custom object while retrieving from the
persistent store.

Resources