简体   繁体   中英

How to correctly migrate old data saved with NSKeyedArchiver into new format while migrating from Objective-C to Swift

An older version of the app has a NSCoding -compliant class (lets call it OldItem ), written in Objective C. This class has about 20 properties with different types:

@property (nonatomic) NSInteger x;

// ...

- (id)initWithCoder:(NSCoder *)coder {

    if (self = [super init])  {
        _x = [coder decodeIntegerForKey:@"x"];
        // all 20 fields are decoded in the similar way
    }

    return self;
}

- (void)encodeWithCoder:(NSCoder *)coder {
    [self.x encodeWithCoder:coder];
    // all 20 fields encoded in the similar way
}

It's used in another class (lets call it Controller ) as array:

@property (nonatomic, strong, nonnull) NSMutableArray<OldItem *> *items;

Controller also caches items to disk like this:

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.items];
[data writeToFile:@"myfile.txt" options:NSDataWritingFileProtectionComplete error:nil];

Now I need to migrate OldItem to Swift, while Controller only changes the type of item it handles:

@property (nonatomic, strong, nonnull) NSMutableArray<NewItem *> *items; 

Question is: how do I migrate an old data, saved in myfile.txt in previous version of the application into new version?

  • Ideally new class has a different name, eg NewItem , but I'm open to keeping the old name, if it significantly simplifies the migration.
  • Don't care if new version will be saving data into the same file, or reading an old file once, and moving data over to a new file.
  • For this question, ignore any differences between OldItem and NewItem . Treat them as direct port.
  • Clearly NewItem should still be NSCoding -compliant, and reading/writing NewItem into the file is not an issue.

There is nothing to migrate. NSCoder is a Cocoa thing, not a language thing; it works just the same whether you talk to it in Objective C or Swift. Just translate all the code line for line into Swift and you're done. The old archived data keeps right on working, because an archive is still an archive and a coder is still a coder. Edit If the question is how to give your Swift class the old Objective C class's name so that the archive can be read into it, note that Objective C MyClass and Swift MyClass have different names because of Swift name mangling; you have to say

@objc(MyClass) class Whatever : NSObject {

After looking around for a bit, the actual solution is:

  1. Make sure public required init?(coder: NSCoder) in NewItem class properly unarchives all fields you want to preserve from OldItem. For instance enums, or number type conversions may require special handling, even if you didn't change fields per se.

  2. Right before unarchiving, tell NSKeyedUnarchiver that NewItem should be used in place of OldItem, using NSKeyedUnarchiver.setClass method.

Example:

let archivedData = try Data(contentsOf: filePath)
NSKeyedUnarchiver.setClass(NewItem.self, forClassName: "OldItem")
let items = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(archivedData)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM