Reputation: 228
I have been banging my head on my monitor for weeks on this, and after taking a break by skirting around my issue, I find I'm running in to the very same issue all over again.
First off, the closest thing I could find relating to my issue: NSCoding / Realm in Google Groups
I am attempting to use the NSCoding protocol on a few of my Realm Model objects so as to turn them into standard NSData objects that I can transfer around in a few frameworks my project is using. However, I am receiving strange errors that I simply cannot find a way around, and which I am just not experience enough to work through.
I am using a simple NSKeyedArchiver, and specifically, archivedDataWithRootObject. The process of encoding seems to go perfectly smoothly, down to the fact that I can transmit its binary blob as NSData without issue - byte for byte, the size on encoding is what is received on the decoder's end. My model is defined as such:
//
// CSTaskRealmModel.m
// CommSync
//
// Created by Ivan Lugo on 1/27/15.
// Copyright (c) 2015 AppsByDLI. All rights reserved.
//
#import "CSTaskRealmModel.h"
@implementation CSTaskRealmModel
#pragma mark - Realm modeling protocol
+ (NSDictionary *)defaultPropertyValues {
NSDictionary* defaults = nil;
defaults = @{@"taskDescription":@"",
@"taskTitle":@"",
@"taskPriority":[NSNumber numberWithInt:0],
@"UUID":@"",
@"deviceID":@"",
@"concatenatedID":@"",
@"assignedID":@"",
@"tag":@"",
@"completed":@false
};
return defaults;
}
+ (NSArray*)ignoredProperties {
return @[@"TRANSIENT_audioDataURL"];
}
+ (NSString*)primaryKey {
return @"concatenatedID";
}
#pragma mark - NSCoding Compliance
- (id) initWithCoder:(NSCoder *)aDecoder {
if(self = [super init]) {
self.UUID = [aDecoder decodeObjectForKey:kUUID];
self.deviceID = [aDecoder decodeObjectForKey:kDeviceId];
self.concatenatedID = [aDecoder decodeObjectForKey:kConcatenatedID];
self.assignedID = [aDecoder decodeObjectForKey:kAssignedID];
self.tag = [aDecoder decodeObjectForKey:kTag];
self.taskTitle = [aDecoder decodeObjectForKey:kTaskTitle];
self.taskDescription = [aDecoder decodeObjectForKey:kTaskDescription];
NSNumber* num = [aDecoder decodeObjectForKey:kCompleted];
self.completed = [num boolValue];
num = [aDecoder decodeObjectForKey:kTaskPriority];
self.taskPriority = [num integerValue];
NSMutableArray* dataArray = [aDecoder decodeObjectForKey:kRevisionDataArray];
for(CSTaskRevisionRealmModel* rev in dataArray) {
[self.revisions addObject:rev];
}
dataArray = [aDecoder decodeObjectForKey:kMediaDataArray];
for(CSTaskMediaRealmModel* media in dataArray) {
[self.taskMedia addObject:media];
}
dataArray = [aDecoder decodeObjectForKey:kCommentsDataArray];
for (CSCommentRealmModel* comment in dataArray) {
[self.comments addObject:comment];
}
}
return self;
}
- (void) encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:_UUID forKey:kUUID];
[aCoder encodeObject:_deviceID forKey:kDeviceId];
[aCoder encodeObject:_concatenatedID forKey:kConcatenatedID];
[aCoder encodeObject:_assignedID forKey:kAssignedID];
[aCoder encodeObject:_tag forKey:kTag];
[aCoder encodeObject: [NSNumber numberWithBool:_completed] forKey:kCompleted];
[aCoder encodeObject: _taskTitle forKey:kTaskTitle];
[aCoder encodeObject:_taskDescription forKey:kTaskDescription];
[aCoder encodeObject:[NSNumber numberWithInteger:_taskPriority] forKey:kTaskPriority];
NSMutableArray* revArray = [NSMutableArray arrayWithCapacity:_revisions.count];
for (CSTaskRevisionRealmModel* rev in self.revisions) {
[revArray addObject:rev];
}
[aCoder encodeObject:revArray forKey:kRevisionDataArray];
NSMutableArray* mediaArray = [NSMutableArray arrayWithCapacity:_taskMedia.count];
for (CSTaskMediaRealmModel* media in self.taskMedia) {
[mediaArray addObject:media];
}
[aCoder encodeObject:mediaArray forKey:kMediaDataArray];
NSMutableArray* commentsArray = [NSMutableArray arrayWithCapacity:_comments.count];
for (CSCommentRealmModel* comment in self.comments) {
[commentsArray addObject:comment];
}
[aCoder encodeObject:commentsArray forKey:kCommentsDataArray];
}
@end
However, when the object is in the act of decoding, on these very innocuous line...
NSData* taskData = [NSData dataWithContentsOfURL:localURL];
id newTask = [NSKeyedUnarchiver unarchiveObjectWithData:taskData];
// Where 'localURL' is just an on-disk location of the data object a device has received.
I get this error; it appears in the Realm source code's RLMAccessor.:
*** Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'
It seems to me as if something very, very weird is happening in the model's init method, and I can't make heads or tails of it. In some cases, if I remove the primary key (trying to get around the invalidation perhaps), the error changes to (in the same source file):
'Primary key can't be changed after an object is inserted.'
I simply cannot figure out what's going on here! Here's the kicker: my original implementation, before I managed to add a few properties to it, worked perfectly :
//
// CSTaskRealmModel.m
// CommSync
//
// Created by Ivan Lugo on 1/27/15.
// Copyright (c) 2015 AppsByDLI. All rights reserved.
//
#import "CSTaskRealmModel.h"
@implementation CSTaskRealmModel
#pragma mark - Lifecycle
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init]) {
self.UUID = [aDecoder decodeObjectForKey:@"UUID"];
self.deviceID = [aDecoder decodeObjectForKey:@"deviceID"];
self.concatenatedID = [aDecoder decodeObjectForKey:@"concatenatedID"];
self.taskTitle = [aDecoder decodeObjectForKey:@"taskTitle"];
self.taskDescription = [aDecoder decodeObjectForKey:@"taskDescripion"];
self.taskPriority = [aDecoder decodeIntForKey:@"taskPriority"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.UUID forKey:@"UUID"];
[aCoder encodeObject:self.deviceID forKey:@"deviceID"];
[aCoder encodeObject:self.concatenatedID forKey:@"concatenatedID"];
[aCoder encodeObject:self.taskTitle forKey:@"taskTitle"];
[aCoder encodeObject:self.taskDescription forKey:@"taskDescripion"];
[aCoder encodeInteger:self.taskPriority forKey:@"taskPriority"];
// NOTE!
// This is ALL KINDS OF EXTREMELY INEFFICIENT!
// We should not rearchive and reconvert images we have already worked with
NSMutableArray* tempArrayOfImages = [NSMutableArray arrayWithCapacity:self.TRANSIENT_taskImages.count];
for(UIImage* image in self.TRANSIENT_taskImages) { // for every TRANSIENT UIImage we have on this task
NSData* thisImage = UIImageJPEGRepresentation(image, 0.3); // make a new JPEG data object with some compressed size
[tempArrayOfImages addObject:thisImage]; // add it to our container
}
NSData* archivedImages = [NSKeyedArchiver archivedDataWithRootObject:tempArrayOfImages]; // archive the data ...
[[RLMRealm defaultRealm] beginWriteTransaction];
self.taskImages_NSDataArray_JPEG = archivedImages; // and set the images of this task to the new archive
[[RLMRealm defaultRealm] commitWriteTransaction];
[aCoder encodeObject:self.taskImages_NSDataArray_JPEG forKey:@"taskImages"]; // encode the object and pray
}
+ (NSArray*)ignoredProperties {
return @[@"TRANSIENT_taskImages"];
}
Note that the only important difference that I can see is that there isn't a primaryKey object on the original implementation. The only things I can think of I'm doing wrong are, (a) I'm fundamentally doing something wrong with my NSCoding lines, or perhaps I shouldn't even be NSCoding these realm objects, or (b) somehow, some other areas of my code are doing something to the validation of the Realm object before while it's getting packaged up and shipped out as NSData.
I cannot properly articulate (it's 2:00am, time of posting..) my appreciation for any insight or help you might have about this. If you would like some more code context, I'd be happy to post it up, or point you in the direction of my git repository.
Upvotes: 3
Views: 1045
Reputation: 228
Looks like a bit of a break and a bit more clever debugging was needed to crack this one. I welcome any corrections on this answer, or any more in-depth information!
As it turns out, accessing a Realm object doesn't actually get you the realm model object itself - or at least, not in the manner the NSCoding
protocol expects. During debugging, I found that the class that was unarchived ended up as a RLMAccessor_[myClassName]
. On a hunch, I decided to try something else.
Rather than just grabbing the realm object I was interested in encoding, I instead instantiated a brand new model and assigning all the critical properties to it from the model of interest. Then, without ever adding it to the realm, sending the encoded version of that over the line. Turns out, that worked perfectly.
For some reason, as yet not totally understood by me, that realm accessor that comes back invalidates the object it's pulling data from, meaning it cannot be reused.
My method may not be the most efficient, and may not be the Realm-way of doing things, but it seems to fit my needs : a Realm model object that can act as a transient and a persisted object.
Upvotes: 2