当原始类不可用时,我该如何解码一个对象?

43

我有一个iOS 7应用程序,可以将自定义对象作为文件保存到应用的iCloud文档文件夹中。为此,我使用NSCoding协议。

@interface Person : NSObject <NSCoding>

    @property (copy, nonatomic) NSString *name
    @property (copy, nonatomic) NSString *lastName

@end

在iOS 7版本的应用中,对象序列化功能完美运作:

  1. initWithCoderencodeWithCoder

  2. [NSKeyedArchiver archivedDataWithRootObject:person]

  3. person = NSKeyedUnarchiver unarchiveObjectWithData:(NSData *)theData]

但是我需要将此应用移植到iOS 8,并且这个类将会被编写成Swift语言,并为这个新版本的应用进行'重命名(renamed)’。

class PersonOldVersion: NSObject, NSCoding {
    var name = ""
    var lastName = ""
}

当我尝试解压该对象时,出现了以下错误:

*** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (Person)'

我已经尝试将Swift类'PersonOldVersion'重命名为原始类名('Person'),但仍然失败。

如果原始类名不可用,我该如何解码对象?


@Alterecho在这里遇到了同样的问题(http://stackoverflow.com/a/25473229/2785261),但是没有回应。 - Aнгел
我曾经遇到过类似的情况,请查看我的答案:https://dev59.com/MV0b5IYBdhLWcg3wM-4c#46832840 - Au Ris
5个回答

68

这可能是另一个解决方案,也是我所做的(因为当时我没有使用Swift)。

在我的情况下,我归档了一个名为“City”的类对象,但随后将该类重命名为“CityLegacy”,因为我创建了一个全新的“City”类。

我不得不这样做才能将旧的“City”对象作为“CityLegacy”对象进行解档:

// Tell the NSKeyedUnarchiver that the class has been renamed
[NSKeyedUnarchiver setClass:[CityLegacy class] forClassName:@"City"];

// Unarchive the object as usual
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [defaults objectForKey:@"city"];
CityLegacy *city = [NSKeyedUnarchiver unarchiveObjectWithData:data];

1
这是我需要的解决方案。但是,第一行代码放在哪里?您是在encodeWithCoderinitWithCoder中执行它吗?每次都要执行它们吗?我能否只使用dispatch_once并在我的根视图控制器或应用程序委托中处理所有更改? - Rob
这是一个更好的答案。 - the Reverend
1
@Rob,代码按照顺序执行;首先配置名称到类的映射,然后进行(解)归档。顺便说一句,如果您想存储具有相同名称的两个类,则每次都需要重新配置映射。虽然不建议这样做,但在某些情况下可能会被迫这样做。 - Ferran Maylinch
1
很棒的答案。setClass方法正是我所需要的。 - Andreas Kraft
太棒了!不知道这个方法。 - derpoliuk
显示剩余3条评论

27
如果Objective-C中的类名很重要,那么你需要明确指定名称。否则,Swift会提供一些混淆的名称。

如果Objective-C中的类名很重要,那么你需要明确指定名称。否则,Swift会提供一些混淆的名称。

@objc(Person)
class PersonOldVersion: NSObject, NSCoding {
    var name = ""
    var lastName = ""
}

3
这也解决了创建今日扩展并在两个项目(应用程序本身和今日小部件)之间共享一个类的问题。谢谢! - swilliams

18
这是Ferran Maylinch的回答的Swift翻译。
在一个Swift项目中,我遇到了类似的问题,当时我复制了原始目标以便生产两个构建版本的产品。这两个构建版本需要可以相互使用。
因此,我的应用程序有myapp_light.app和my app_pro.app这样的东西。设置类可以解决这个问题。
NSKeyedUnarchiver.setClass(MyClass1.classForKeyedUnarchiver(), forClassName: "myapp_light.MyClass1")
NSKeyedUnarchiver.setClass(MyClass1.classForKeyedUnarchiver(), forClassName: "myapp_pro.MyClass1")



if let object:AnyObject = NSKeyedUnarchiver.unarchiveObjectWithFile("\path\...") {
    var myobject = object as! Dictionary<String,MyClass1>
    //-- other stuff here
}

1
当您更改类的目标名称或将其移动到框架时,通常会发生这种情况。您知道如何在加载后将类名重置为新名称吗? - João Nunes

5
我认为您可以按如下方式解决它:
@implementation PersonOldVersion

+ (void)load
{
    [NSKeyedUnarchiver setClass:[PersonOldVersion class] forClassName:@"Person"];
}

1

newacct的回答 帮助我解决了类名的问题,但我还遇到了一个问题,我在 Swift 类中更改了变量名,在我的 Objective-C 类中也要做出相应的更改。使用@objc() 同样可以解决这个问题:

旧的类:

@interface JALProgressData : NSObject <NSCoding>

@property (nullable, nonatomic, strong) NSString *platformID;
@property (nonatomic, assign) NSTimeInterval secondsPlayed;
@property (nonatomic, assign) NSTimeInterval totalDuration;
@property (nullable, nonatomic, strong) NSDate *lastUpdated;

@end

新的类:
@objc(JALProgressData)
class VideoProgress: NSObject, NSCoding {
    @objc(platformID) var videoId: String?
    var secondsPlayed: NSTimeInterval?
    var totalDuration: NSTimeInterval?
    var lastUpdated: NSDate?
}

我也意识到我在使用encodeObjectdecodeObjectForKey处理值类型时,会导致我的Swift类出现问题。将NSTimeInterval类型的编码更改为encodeDoubledecodeDoubleForKey可以解决aDecoder返回nil的问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接