How do I convert my NSManagedObject to NSData object?
I'm new to Core Data and the Multipeer Connectivity Framework.
I need to transfer data between 2 devices via the Multipeer Connectivity Framework. I understand that I cannot simply transfer via MPC since it requires an NSData object.
Are there any third-party libraries that provides such function?
I think NSCoding is not such a good idea here. The reason is that the objects will not be the same on two different devices due to their internal managed object IDs as well as a myriad of other possible problems that can occur in unexpected syncing scenarios.
I would strongly recommend to take the trouble and convert your object into a NSDictionary type and then use the standard NSData APIs on the dictionary (or an array of dictionaries).
try like this
You should use NSCoding protocol, then you can encode your object to NSData. Again If you want decode your original object use NSKeyedUnarchiver.
in .h
#interface Testting : NSManagedObject<NSCoding>
and this in .m
NSData *data=[NSKeyedArchiver archivedDataWithRootObject:hereyourObject];
//get your original object
Testting *Obj = [NSKeyedUnarchiver unarchiveObjectWithData:data];
You should use NSCoding protocol. Using NSKeyedAchiever you can encode your object to NSData. Again If you want decode your original object use NSKeyedUnarchiver.
#interface Test : NSManagedObject <NSCoding>
#property (nonatomic, retain) NSString *title;
#end
#implementation Test
#dynamic title;
- (id)initWithCoder:(NSCoder *)coder {
NSEntityDescription *entity =
[NSEntityDescription entityForName:#"Test" inManagedObjectContext:<YourContext>];
self = [super initWithEntity:entity insertIntoManagedObjectContext:nil];
NSArray * attributeNameArray =
[[NSArray alloc] initWithArray:self.entity.attributesByName.allKeys];
for (NSString * attributeName in attributeNameArray) {
[self setValue:[aDecoder decodeObjectForKey:attributeName] forKey:attributeName];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.title forKey:#"title"];
}
#end
//converting to NSData
NSData *archivedObject = [NSKeyedArchiver archivedDataWithRootObject:testObj];
//get your original object
Test *testObj = [NSKeyedUnarchiver unarchiveObjectWithData:archivedObject];
Related
I need to save many NSMutableArray with my custom objects and I want to know what is the best way to do this.
Maybe NSUserDefaults is not the best way to do it.
What should I use?
If your array contains non-plist objects, then you cannot use NSUserDefaults without first encoding the array.
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'll want to encode it using NSKeyedArchiver. This will give you an NSData object that you can then store in NSUserDefaults, or write it to file through NSKeyedArchiver itself.
All you have to do is conform to NSCoding in your custom object, and override initWithCoder: to initialise your object when it's loaded and encodeWithCoder: to encode your variables when it gets encoded. For example, your custom object will look something like this:
#interface customArrayObject : NSObject <NSCoding>
#property (nonatomic) NSString* foo;
#property (nonatomic) NSInteger bar;
#end
#implementation customArrayObject
-(instancetype) initWithCoder:(NSCoder *)aDecoder { // decode variables
if (self = [super init]) {
_foo = [aDecoder decodeObjectForKey:#"foo"];
_bar = [aDecoder decodeIntegerForKey:#"bar"];
}
return self;
}
-(void) encodeWithCoder:(NSCoder *)aCoder { // encode variables
[aCoder encodeObject:_foo forKey:#"foo"];
[aCoder encodeInteger:_bar forKey:#"bar"];
}
#end
It's also worth noting that NSUserDefaults is used to store user preferences, and therefore if your array contains data that isn't in any way to do with a user preference, you shouldn't be using NSUserDefaults - you should be writing it to disk yourself.
Writing your array to disk is actually a lot more trivial than it sounds, you can use the archiveRootObject:toFile: method on NSKeyedArchiver. For example, this will write your custom array to the documents directory:
// Gets the documents directory path
NSString* documentsPath = NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES)[0];
// Archive and save the file to foo.dat in the documents directory. Returns whether the operation was successful.
BOOL success = [NSKeyedArchiver archiveRootObject:customArray toFile:[NSString stringWithFormat:#"%#/%#", documentsPath, #"foo.dat"]]
However, it is also worth noting that this flat (yes/no) as to whether the operation was successful isn't that great when it comes to error handling. If you want to implement custom error handling, then you'll want to first encode the object using NSKeyedArchiver's archivedDataWithRootObject: method, and then NSData's writeToFile:options:error: method.
I am trying to archive an NSObject that I send through match data in a game center turn based game.
Below is my code for archiving my object
turnDataObject MyData = [[turnDataObject alloc] init];
data = [NSKeyedArchiver archivedDataWithRootObject:MyData];
This is my code for unarchiving my object
readMyData = [NSKeyedUnarchiver unarchiveObjectWithData:data] ;
However when I run this code I get an error
thread 1 exc bad access code
I think that this might have to do with sending addresses when I archive data. How do I send something that will be readable when I unarchive it?
Edit 1: I get the error on the next line after I unarchive. it says that the adress I am trying to access is null. I remember reading somewhere that I souldn't send adresses of my NSObject but I am not sure how to convert it to something else.
readMyData = [NSKeyedUnarchiver unarchiveObjectWithData:data] ;
NSLog(#"current game happens to be: %#", readMyData.currentGame);
Edit 2: here is my init with coder and encode with coder
- (instancetype)initWithCoder:(NSCoder *)decoder
{
self = [self init];
if (self) {
_currentGame = [decoder decodeObjectForKey:currentGameDataKey];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
//scores data keys
[encoder encodeObject:self.currentGame forKey:currentGameDataKey];
}
Edit 3: _currentGame is in my objects .h file
#property (assign, nonatomic) NSString *currentGame;
I would suggest creating NSKeyedArchiver and NSKeyedUnarchiver objects and using those instead of using the type (which it looks like you're doing.)
I usually program in Swift but here's a shot at example code:
theArchiver NSKeyedArchiver = [[theArchiver alloc] init];
data = [theArchiver archivedDataWithRootObject:MyData];
Then you would do the same thing with the NSKeyedUnarchiver.
Your initWithCoder implementation is wrong:
self = [self init];
That should be:
self = [super init];
You need to add NSCoding protocol to MyData Class, here is the code snippet with supporting NSCoding in order to add Archiving support to NSObjet.
MyData.h
#interface MyData : NSObject <NSCoding>
#property (nonatomic, strong) NSString *currentGame;
#end
MyData.m
//This method is optional, if you need constructor for current game
- (instancetype)initWithCurrentGame:(NSDictionary *)currentGame {
self = [super init];
if (self) {
self.currentGame = currentGame;
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.currentGame forKey:#"currentGame"];
}
-(id)initWithCoder:(NSCoder *)decoder {
self.currentGame = [decoder decodeObjectForKey:#"currentGame"];
return self;
}
I'm trying to store a NSMutableArray consisting of VOs (NameVO) with Core Data but getting the following exception thrown:
'NSInvalidArgumentException', reason: '-[NameVO encodeWithCoder:]: unrecognized selector sent to instance 0x1096400a0'
My NameVO is just a simple value object, extending NSObject and contains two fields, a string and a NSMutableArray that itself contains strings. It also contains a compare function.
I'm trying to prepare this to be stored as a CD transformable attribute type with ('names' is my NameVO array):
NSData *tempNamesData = [NSKeyedArchiver archivedDataWithRootObject:names];
My question is: what do I need to do to make NameVO be accepted by NSKeyedArchiver to convert it successfully to a NSData?
I don't want NameVO to extend NSManagedObject because then I cannot instantiate and init it directly.
Now that we're in 2017, it's best to use the safer NSSecureCoding protocol instead of the older, less safe NSCoding protocol. The implementation changes are minimal:
1) ensure that your class declares its conformation to NSSecureCoding
#interface MyClass : NSObject<NSSecureCoding>
#property (nonatomic, strong) NSNumber *numberProperty;
#property (nonatomic, strong) NSString *stringProperty;
#end
2) NSSecureCoding protocol houses the same two instance methods methods in the NSCoding protocol, plus an additional class method, +supportsSecureCoding. You'll need to add that method, as well as slightly modify your -initWithCoder: method.
#implementation MyClass
// New Method for NSSecureCoding. Return YES.
+ (BOOL)supportsSecureCoding {
return YES;
}
// Your Encode Method Can Stay The Same, though I would use NSStringFromSelector whenever possible to get the keys to ensure you're always getting what you're looking for.
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.numberProperty forKey:NSStringFromSelector(#selector(numberProperty))];
[aCoder encodeObject:self.stringProperty forKey:NSStringFromSelector(#selector(stringProperty))];
}
// Slightly updated decode option
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.numberProperty = [aDecoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(#selector(numberProperty))];
self.stringProperty = [aDecoder decodeObjectOfClass:[NSString class] forKey:NSStringFromSelector(#selector(stringProperty))];
}
}
#end
Notice that NSCoder's -decodeObjectOfClass:withKey: requires you to specify the class that you're expecting to receive. This is a much safer way to do things.
Then, to store this decodable object in CoreData, simply create a Managed object that contains an NSData attribute and some identifying information (a string, a date, an id, or a number or something)
#interface MyClassMO : NSManagedObject
#property (nonatomic, strong) NSString *identifier;
#property (nonatomic, strong) NSData *data;
#end
#implementation MyClassMO
#dynamic identifier;
#dynamic data;
#end
In practice, it would look something like this:
- (void)storeObject:(MyClass *)object withIdentifier:(NSString *)identifier {
NSData *objectData = [NSKeyedArchived archivedDataWithRootObject:object];
NSManagedObjectContext *moc = ... // retrieve your context
// this implementation relies on new NSManagedObject initializers in the iOS 10 SDK, but you can create it any way you typically create managed objects
MyClassMO *managedObject = [[MyClassMO alloc] initWithContext:moc];
managedObject.data = objectData;
managedObject.identifier = identifier;
NSError *error;
[moc save:&error];
}
- (MyClass *)retrieveObjectWithIdentifier(NSString *)identifier {
NSManagedObject *moc = ... // retrieve your context
// This also relies on iOS 10 SDK's new factory methods available on NSManagedObject. You can create your request any way you typically do;
NSFetchRequest *request = [MyClassMO fetchRequest];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"identifier = %#", identifier];
request.predicate = predicate;
NSError *error;
NSArray<MyClassMO *> *results = [moc executeFetchRequest:request withError:&error];
// if you're only storing one object per identifier, this array should only be 1 object long. if not, you'll need to decide which object you're looking for. you also might want to implement an overwrite policy or a delete before store type thing.
MyClassMO *managedObject = results.firstObject;
NSData *objectData = managedObject.data;
MyClass *object = [NSKeyedUnarchiver unarchiveObject:objectData];
return object;
}
This solution is obviously a bit of an oversimplification, how and when you store stuff in the db is up to your needs, but the main idea is that, you'll need to make sure your custom class conforms to NSSecureCoding, and that you'll need to make a separate Managed Object class to store and retrieve your data.
As your exception says you need to implement NSCoding protocol to your class and you have to override two methods:
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;
It should sorted your issue.
// EXTENDED
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
_formId = [aDecoder decodeObjectForKey:#"FormID"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.formId forKey:#"FormID"];
}
Use this initWithCoder and encodeWithCode method.I hope it will work for you. it works for me in same issue as u have...Use this sample code
-(id)initWithCoder:(NSCoder *)aDecoder
{
if(self = [super init]){
storePlaylist=[aDecoder decodeObjectForKey:#"storePlaylist"];
playlistName=[aDecoder decodeObjectForKey:#"playlistName"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:storePlaylist forKey:#"storePlaylist"];
[encoder encodeObject:playlistName forKey:#"playlistName"];
}
In my application i have an custom NSObject, which contains 2 mutable Arrays.
I need to save this custom NSOBject into a core data entity, but i have no real idea how i can accomplish that...
After some searching, i found out, that the best way would be to convert the nsobject to nsdata and save it in an transformable field of the entity... but i m not sure how to do that.
can someone help me?
heres to code for my custom object:
MeasureData.h
#interface MeasureData : NSObject{
}
#property (nonatomic, strong) NSMutableArray *questionsData;
#property (nonatomic, strong) NSMutableArray *answersData;
- (id) init;
#end
MeasureData.m
#import "MeasureData.h"
#implementation MeasureData
#synthesize questionsData;
#synthesize answersData;
#pragma mark -
#pragma mark int
- (id)init
{
self = [super init];
// Initalize questions array (width data from plist)
questionsData = self.makeQuestionsArray;
// NSLog(#"loaded questions array: %#",questionsData); // debug
// Initalize answers array
answersData = self.makeAnswersArray;
// NSLog(#"loaded answers array: %#",answersData); // debug
return self;
}
-(NSMutableArray *)makeQuestionsArray
{
// Initalize questions array (width data from plist)
NSString *path = [[NSBundle mainBundle] pathForResource:
#"questions.list" ofType:#"plist"];
NSMutableArray *questions = [NSMutableArray arrayWithContentsOfFile:path];
/*
[questionsData insertObject:(NSString *)string atIndex:0];
*/
return questions;
}
-(NSMutableArray *)makeAnswersArray
{
// Initalize answers array
NSMutableArray *answers = [NSMutableArray arrayWithCapacity:0];
return answers;
}
-(id)initWithCoder:(NSCoder *)decoder {
if ((self=[super init])) {
questionsData = [decoder decodeObjectForKey:#"questionsData"];
answersData = [decoder decodeObjectForKey:#"answersData"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:questionsData forKey:#"questionsData"];
[encoder encodeObject:questionsData forKey:#"questionsData"];
}
#end
According to the first comment, i implemented the encoder/coder functions for my custom class. And tried to archive and encode it (i m new to ios, so it could be completly wrong) - but it dont work... can someone tell me whats wrong?
heres the encoding (which dont work XD):
NSMutableData *dataToSave = (NSMutableData *)self.measureData;
NSKeyedArchiver *archiverForData = [[NSKeyedArchiver alloc] initForWritingWithMutableData:dataToSave];
[archiverForData encodeObject:dataToSave forKey:#"dataToSave"];
[archiverForData finishEncoding];
//
//theMeasure is the CoreData Entity
theMeasure.result = dataToSave;
In outline:
create a NSMutableData
create a NSKeyedArchiver with initForWritingWithMutableData over your data
serialize your arrays / objects / whatever you need (that implements NSCoding) with encode... methods of NSCoder
create a managed object with a BLOB (binary data) type field
write your encoded data from the mutable data to this field of the managed object.
In my answer to this question you can find some useful links: NSCoding VS Core data
I'd like to save an NSMutableDictionary object in NSUserDefaults. The key type in NSMutableDictionary is NSString, the value type is NSArray, which contains a list of object which implements NSCoding. Per document, NSString and NSArray both are conform to NSCoding.
I am getting this error:
[NSUserDefaults setObject: forKey:]: Attempt to insert non-property value.... of class NSCFDictionary.
I found out one alternative, before save, I encode the root object (NSArray object) using NSKeyedArchiver, which ends with NSData. Then use UserDefaults save the NSData.
When I need the data, I read out the NSData, and use NSKeyedUnarchiver to convert NSData back to the object.
It is a little cumbersome, because i need to convert to/from NSData everytime, but it just works.
Here is one example per request:
Save:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *arr = ... ; // set value
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:arr];
[defaults setObject:data forKey:#"theKey"];
[defaults synchronize];
Load:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [defaults objectForKey:#"theKey"];
NSArray *arr = [NSKeyedUnarchiver unarchiveObjectWithData:data];
The element in the array implements
#interface CommentItem : NSObject<NSCoding> {
NSString *value;
}
Then in the implementation of CommentItem, provides two methods:
-(void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:value forKey:#"Value"];
}
-(id)initWithCoder:(NSCoder *)decoder
{
self.value = [decoder decodeObjectForKey:#"Value"];
return self;
}
Anyone has better solution?
Thanks everyone.
If you're saving an object in user defaults, all objects, recursively, all the way down, must be property list objects. Conforming to NSCoding doesn't mean anything here-- NSUserDefaults won't automatically encode them into NSData, you have to do that yourself. If your "list of object which implements NSCoding" means objects that are not property list objects, then you'll have to do something with them before saving to user defaults.
FYI the property list classes are NSDictionary, NSArray, NSString, NSDate, NSData, and NSNumber. You can write mutable subclasses (like NSMutableDictionary) to user preferences but the objects you read out will always be immutable.
Are all of your keys in the dictionary NSStrings? I think they have to be in order to save the dictionary to a property list.
Simplest Answer :
NSDictionary is only a plist object , if the keys are NSStrings.
So, Store the "Key" as NSString with stringWithFormat.
Solution :
NSString *key = [NSString stringWithFormat:#"%#",[dictionary valueForKey:#"Key"]];
Benefits :
It will add String-Value.
It will add Empty-Value when your Value of Variable is NULL.
Have you considered looking at implementing the NSCoding Protocol? This will allow you encode and decode on the iPhone with two simple methods that are implemented with the NSCoding. First you would need to adding the NSCoding to your Class.
Here is an example:
This is in the .h file
#interface GameContent : NSObject <NSCoding>
Then you will need to implement two methods of the NSCoding Protocol.
- (id) initWithCoder: (NSCoder *)coder
{
if (self = [super init])
{
[self setFoundHotSpots:[coder decodeObjectForKey:#"foundHotSpots"]];
}
return self;
}
- (void) encodeWithCoder: (NSCoder *)coder
{
[coder encodeObject:foundHotSpots forKey:#"foundHotSpots"];
}
Check out the documentation on NSCoder for more information. That has come in really handy for my projects where I need to save the state of the application on the iPhone if the application is closed and restore it back to it's state when its back on.
The key is to add the protocol to the interface and then implement the two methods that are part of NSCoding.
I hope this helps!
There is no better solution. Another option would be to just save the coded object to disk - but that is doing the same thing. They both end up with NSData that gets decoded when you want it back.