NSManagedObject 能否符合 NSCoding

Posted

技术标签:

【中文标题】NSManagedObject 能否符合 NSCoding【英文标题】:Can NSManagedObject conform to NSCoding 【发布时间】:2013-05-08 13:08:23 【问题描述】:

我需要跨设备传输单个对象。现在我正在将我的 NSManagedObject 转换为字典,将其存档并作为 NSData 发送。收到后,我正在取消存档。但我真的很想通过归档和取消归档而不是创建中间数据对象来传输 NSManagedObject 本身。

@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];

上面代码的问题是。它在initWithCoder 中的self.title 处崩溃,表示将无法识别的选择器发送到实例。

为什么title 未被识别为选择器。 是否应该在 initWithCoder 中创建对象之前以某种方式取消归档使用 nil 托管对象上下文? 我需要覆盖copyWithZone吗?

【问题讨论】:

来自docs:`如果直接实例化一个托管对象,则必须调用指定的初始化器(initWithEntity:insertIntoManagedObjectContext:)` 【参考方案1】:

下面的这个 sn-p 应该可以解决问题。主要区别是调用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;

上面的 sn-p 将只处理属性,不处理关系。使用NSCoding 处理NSManagedObjectID 的关系是可怕的。如果您确实需要跨越关系,请考虑在解码时引入一个额外的属性来匹配两个(或多个)实体。

如何获得&lt;YourContext&gt;

(基于 Sam Soffes 的一篇现已不可用的帖子,代码取自 https://gist.github.com/soffes/317794#file-ssmanagedobject-m)

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

注意:将第一个sn-p中的&lt;YourContext&gt;替换为mainContext

【讨论】:

如何获取 的链接已损坏。可以更新吗? 原站点离线,但我发现了一个旧要点并从那里复制了代码。我希望这会有所帮助 托管上下文是否有意在这一行中为零? self = [super initWithEntity:entity insertIntoManagedObjectContext:nil];【参考方案2】:

显然NSManagedObject 不符合NSCoding。您可以尝试使自定义托管对象子类符合要求,但这充其量只是一个冒险的提议。 NSManagedObject 必须有一个相关的 NSManagedObjectID。而且,您无需分配对象 ID——创建对象时会自动发生。即使您使您的子类符合NSCoding,您也必须找到一种方法来取消归档对象,同时还允许本地托管对象上下文分配对象 ID。

甚至忽略了如何处理托管对象上的关系的问题。

NSDictionary 相互转换确实是一种更好的方法。但是您不能只是取消归档数据并完成。在接收端,您需要创建一个新的托管对象实例并从字典中设置其属性值。 可能可以让你的方法发挥作用,但是当你完成时,它会比你只使用NSDictionary 做更多的工作和更多的代码。

说真的:NSCodinginitWithCoder:copyWithZone: 等对于您要解决的问题来说是一个非常糟糕的主意。 NSCoding 适用于许多情况,但在这里不合适。

【讨论】:

同意 NSManagedObject 上的 NSCoding 是个坏主意。传输/序列化 NSManagedObject 的通用方法应该是可能的。【参考方案3】:

问题显然是取消归档。最后没有办法在同一个对象中同时使用initWithEntity:initWithCoder:。但是,我怀疑通过一些技巧,您也许可以完成这项工作。例如,按照您的做法实现initWithCoder:,然后使用initWithEntity: 创建另一个托管对象(这意味着您将需要可以保存此类引用的非托管ivars。实现forwardingTargetForSelector:,如果该对象是那个对象使用initWithCoder:创建的,将其转发到您使用initWithEntity:创建的影子对象(否则,将该选择器转发给super)。当对象完全解码后,然后向它询问真正的托管对象,您就完成了.

注意:我没有这样做,但在forwardingTargetForSelector: 上取得了巨大的成功。

【讨论】:

以上是关于NSManagedObject 能否符合 NSCoding的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 NSManagedObject 不符合 KVC?

NSManagedObject 不能符合 Swift 中的协议

来自 nsmanagedObject 的 NSJSONSerialization 表示键不符合键值编码

如何让我的简单对象符合 Swift 中的 NSManagedObject 和 NSCoding

UITableView "cellForRowAt: indexPath" 偶尔在 Core Data 属性上调用 "init?(coder aDecoder: NSCo

创建 NSManagedObject 的副本