web-dev-qa-db-fra.com

Comment puis-je décoder un objet lorsque la classe d'origine n'est pas disponible?

J'ai une application iOS 7 qui enregistre un objet personnalisé dans le dossier iCloud Docs de l'application en tant que fichier. Pour cela, j'utilise le protocole NSCoding.

@interface Person : NSObject <NSCoding>

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

@end

La sérialisation d'objets fonctionne parfaitement dans la version iOS 7 de l'application:

  1. initWithCoder et encodeWithCoder

  2. [NSKeyedArchiver archivedDataWithRootObject:person]

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

Mais je dois déplacer cette application vers iOS 8, et cette classe sera codée en Swift et "renommée" pour cette nouvelle version iOS 8 de l'application.

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

Lorsque j'essaie de désarchiver l'objet, j'ai eu l'erreur suivante:

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

J'ai déjà essayé de renommer Swift class 'PersonOldVersion' en nom de classe d'origine ('Person') mais échoue toujours.

Comment puis-je décoder un objet dont la classe d'origine n'est pas disponible?

39
Aнгел

Si le nom de la classe dans Objective-C est important, vous devez spécifier explicitement le nom. Sinon, Swift fournira un nom déformé.

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

Cela pourrait être une autre solution, et c'est ce que j'ai fait (puisque je n'ai pas utilisé Swift à ce moment-là).

Dans mon cas, j'ai archivé un objet de la classe "City" mais j'ai ensuite renommé la classe en "CityLegacy" car j'ai créé une classe "City" complètement nouvelle.

J'ai dû faire cela pour désarchiver l'ancien objet "City" en tant qu'objet "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];
64
Ferran Maylinch

Voici une Swift pour la réponse de Ferran Maylinch ci-dessus.

J'ai eu un problème similaire dans un projet Swift après avoir dupliqué la cible d'origine afin d'avoir 2 versions de mon produit. Les 2 versions devaient être utilisables de manière interchangeable.

Donc, j'avais quelque chose comme myapp_light.app et mon app_pro.app. La définition de la classe a résolu ce problème.

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
}
15
nspire

Je pense que vous pouvez le résoudre comme suit:

@implementation PersonOldVersion

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

réponse de newacct m'a aidé avec le nom de la classe, mais j'ai également eu un problème où j'ai changé un nom de variable dans ma classe Swift de ma classe Objective-C. Utilisation de @objc() a corrigé cela aussi:

Ancienne classe:

@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

Nouvelle classe:

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

J'ai également réalisé que j'utilisais encodeObjectdecodeObjectForKey pour les types de valeur, ce qui posait des problèmes avec ma Swift. Passer à encodeDouble et decodeDoubleForKey pour les types NSTimeInterval fixes aDecoder renvoyant nil pour ces valeurs.

1
JAL