将 NSCoding 与领域模型对象一起使用
Using NSCoding with Realm Model Objects
几周来,我一直在用我的显示器敲打我的头,在绕过我的问题休息一下后,我发现我 运行 又陷入了同样的问题。
首先,我能找到的与我的问题最接近的东西:
NSCoding / Realm in Google Groups
我正在尝试在我的一些领域模型对象上使用 NSCoding 协议,以便将它们变成标准的 NSData 对象,我可以在我的项目使用的一些框架中传输这些对象。但是,我收到了一些奇怪的错误,我根本找不到解决办法,而且我的经验还不够,无法解决。
我正在使用一个简单的 NSKeyedArchiver,特别是 archivedDataWithRootObject。编码过程似乎进行得非常顺利,事实上我可以毫无问题地将其二进制 blob 作为 NSData 进行传输 - 字节接字节,编码的大小是解码器端接收到的大小。我的模型是这样定义的:
//
// 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
然而,当对象在解码时,在这些非常无害的行上...
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.
我收到这个错误;它出现在 Realm 源代码的 RLMAccessor.:
*** Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'
在我看来,模型的 init 方法中似乎发生了一些非常非常奇怪的事情,我无法弄清它的正反面。在某些情况下,如果我删除主键(可能试图绕过失效),错误将更改为(在同一源文件中):
'Primary key can't be changed after an object is inserted.'
我简直搞不懂这是怎么回事!关键是:在我设法向其添加一些属性之前,我的原始实现完美 :
//
// 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"];
}
请注意,我能看到的唯一重要区别是原始实现中没有 primaryKey 对象。我唯一能想到我做错的是,(a)我从根本上说我的 NSCoding 行有问题,或者我什至不应该对这些领域对象进行 NSCoding,或者(b)不知何故,其他一些在将 Realm 对象打包并作为 NSData 发送出去之前,我的代码区域正在对 Realm 对象进行一些事情 的验证。
我无法正确表达(现在是 2:00am,posting 的时间..)我感谢您对此提供的任何见解或帮助。如果您想要更多代码上下文,我很乐意 post 将其上传,或者为您指明我的 git 存储库的方向。
看起来有点中断,需要更巧妙的调试才能破解此问题。我欢迎对此答案的任何更正,或任何更深入的信息!
事实证明,访问 Realm 对象 实际上 并不能为您提供领域模型对象本身 - 或者至少,不会以 NSCoding
的方式协议预计。在调试过程中,我发现未归档的 class 最终变成了 RLMAccessor_[myClassName]
。凭直觉,我决定尝试别的东西。
我不是仅仅获取我对编码感兴趣的领域对象,而是实例化了一个全新的模型,并将感兴趣的模型中的所有关键属性分配给它。然后,without 将它添加到领域,通过线路发送它的编码版本。事实证明,效果很好。
出于某种原因,我还没有完全理解,返回的领域访问器使它从中提取数据的对象无效,这意味着它不能被重用。
我的方法可能不是最有效的,也可能不是 Realm 的做事方式,但它似乎符合我的需要:一个 Realm 模型对象,可以充当瞬态对象和持久对象。
几周来,我一直在用我的显示器敲打我的头,在绕过我的问题休息一下后,我发现我 运行 又陷入了同样的问题。
首先,我能找到的与我的问题最接近的东西: NSCoding / Realm in Google Groups
我正在尝试在我的一些领域模型对象上使用 NSCoding 协议,以便将它们变成标准的 NSData 对象,我可以在我的项目使用的一些框架中传输这些对象。但是,我收到了一些奇怪的错误,我根本找不到解决办法,而且我的经验还不够,无法解决。
我正在使用一个简单的 NSKeyedArchiver,特别是 archivedDataWithRootObject。编码过程似乎进行得非常顺利,事实上我可以毫无问题地将其二进制 blob 作为 NSData 进行传输 - 字节接字节,编码的大小是解码器端接收到的大小。我的模型是这样定义的:
//
// 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
然而,当对象在解码时,在这些非常无害的行上...
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.
我收到这个错误;它出现在 Realm 源代码的 RLMAccessor.:
*** Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'
在我看来,模型的 init 方法中似乎发生了一些非常非常奇怪的事情,我无法弄清它的正反面。在某些情况下,如果我删除主键(可能试图绕过失效),错误将更改为(在同一源文件中):
'Primary key can't be changed after an object is inserted.'
我简直搞不懂这是怎么回事!关键是:在我设法向其添加一些属性之前,我的原始实现完美 :
//
// 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"];
}
请注意,我能看到的唯一重要区别是原始实现中没有 primaryKey 对象。我唯一能想到我做错的是,(a)我从根本上说我的 NSCoding 行有问题,或者我什至不应该对这些领域对象进行 NSCoding,或者(b)不知何故,其他一些在将 Realm 对象打包并作为 NSData 发送出去之前,我的代码区域正在对 Realm 对象进行一些事情 的验证。
我无法正确表达(现在是 2:00am,posting 的时间..)我感谢您对此提供的任何见解或帮助。如果您想要更多代码上下文,我很乐意 post 将其上传,或者为您指明我的 git 存储库的方向。
看起来有点中断,需要更巧妙的调试才能破解此问题。我欢迎对此答案的任何更正,或任何更深入的信息!
事实证明,访问 Realm 对象 实际上 并不能为您提供领域模型对象本身 - 或者至少,不会以 NSCoding
的方式协议预计。在调试过程中,我发现未归档的 class 最终变成了 RLMAccessor_[myClassName]
。凭直觉,我决定尝试别的东西。
我不是仅仅获取我对编码感兴趣的领域对象,而是实例化了一个全新的模型,并将感兴趣的模型中的所有关键属性分配给它。然后,without 将它添加到领域,通过线路发送它的编码版本。事实证明,效果很好。
出于某种原因,我还没有完全理解,返回的领域访问器使它从中提取数据的对象无效,这意味着它不能被重用。
我的方法可能不是最有效的,也可能不是 Realm 的做事方式,但它似乎符合我的需要:一个 Realm 模型对象,可以充当瞬态对象和持久对象。