Can NSManagedObject conform to NSCoding
Asked Answered
R

3

6

I need to transfer a single object across device. Right now I am converting my NSManagedObject to a dictionary , archiving it and sending as NSData. Upon receiving I am unarchiving it. But I would really like to transfer the NSManagedObject itself by archiving and unarchiving instead of creating an intermediate data object.

@interface Test : NSManagedObject<NSCoding>
@property (nonatomic, retain) NSString * title;
@end

@implementation Test
@dynamic title;

- (id)initWithCoder:(NSCoder *)coder {
    self = [super init];
    if (self) {
        self.title = [coder decodeObjectForKey:@"title"]; //<CRASH
    }
    return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject:self.title forKey:@"title"];
}
@end


NSData *archivedObjects = [NSKeyedArchiver archivedDataWithRootObject:testObj];
NSData *objectsData = archivedObjects;
if ([objectsData length] > 0) {
    NSArray *objects = [NSKeyedUnarchiver unarchiveObjectWithData:objectsData];
}

The problem with the above code is. It crashes at self.title in initWithCoder saying unrecognized selector sent to instance.

  • Why is title not being recognized as a selector.
  • Should unarchive use a nil managed object context somehow before creating the object in initWithCoder?
  • Do i need to override copyWithZone?
Rael answered 8/5, 2013 at 13:8 Comment(1)
from the docs: ` If you instantiate a managed object directly, you must call the designated initializer (initWithEntity:insertIntoManagedObjectContext:)`Sukey
D
8

This snippet below should do the trick. The main difference is to call super initWithEntity:insertIntoManagedObjectContext:

- (id)initWithCoder:(NSCoder *)aDecoder {
   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;
}

Above snippet will handle only the attributes, no relationships. Dealing with relationships as NSManagedObjectID using NSCoding is horrible. If you do need to bring relationships across consider introducing an extra attribute to match the two (or many) entities when decoding.

how to obtain <YourContext>

(based on a now unavailable post by Sam Soffes, code taken from https://gist.github.com/soffes/317794#file-ssmanagedobject-m)

+ (NSManagedObjectContext *)mainContext {
     AppDelegate *appDelegate = [AppDelegate sharedAppDelegate];
return [appDelegate managedObjectContext];
}

Note: replace <YourContext> in the first snippet with mainContext

Duple answered 9/5, 2013 at 8:20 Comment(3)
The link on how to obtain <YourContext> is broken. Can you update it?Utile
The original site is offline, but I found an a old gist and have copied the code from there. I hope this helpsDuple
Is managed context intentionally nil in this line? self = [super initWithEntity:entity insertIntoManagedObjectContext:nil];Utile
G
2

Obviously NSManagedObject does not conform to NSCoding. You could try to make a custom managed object subclass conform, but it would be a dicey proposition at best. An NSManagedObject must have a related NSManagedObjectID. And, you don't get to assign the object ID-- that happens automatically when the object is created. Even if you made your subclass conform to NSCoding, you'd have to find a way to unarchive the object while also allowing the local managed object context to assign an object ID.

And even that ignores the question of how you'd handle relationships on your managed objects.

Converting to/from an NSDictionary is really a much better approach. But you can't just unarchive the data and be finished. On the receiving end, you need to create a new managed object instance and set its attribute values from the dictionary. It might be possible to get your approach to work, but by the time you're done it will be more work and more code than if you just used an NSDictionary.

Seriously: NSCoding, initWithCoder:, copyWithZone:, etc, are a really bad idea for the problem you're trying to solve. NSCoding is nice for many situations but it's not appropriate here.

Galoshes answered 8/5, 2013 at 16:42 Comment(1)
agreed that NSCoding on a NSManagedObject is a bad idea. generic way to transfer/serialize a NSManagedObject should be possible.Rael
V
0

The problem is obviously the unarchiver. In the end there is no way to use both initWithEntity: and initWithCoder: in the same object. However, I suspect that with some trickery you may be able to make this work. For instance, implement initWithCoder: as you have done, and in that create another managed object with initWithEntity: (this means you will need unmanaged ivars that can hold such a reference. Implement forwardingTargetForSelector:, and if the object is the one being created using initWithCoder:, forward it to the shadow object you created with initWithEntity: (otherwise, forward that selector to super). When the object is decoded fully, then ask it for the real managed object, and you're done.

NOTE: I have not done this but have had great success with forwardingTargetForSelector:.

Vulgate answered 8/5, 2013 at 13:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.