Search code examples
iosmigrationnscodingnskeyedarchiverobject-persistence

How to migrate an object which has been persisted with NSKeyedArchiver?


I'm using a version of Archiver and have run into an issue.

In a previous version of my project a class, Challenge was serialized to disk

//v1.0
@interface Challenge : NSObject<NSCoding>
{
  ...
  @property (nonatomic,strong) NSString *challengeId;
  @property (nonatomic,strong) NSString *title;
  @property (nonatomic) BOOL isCompleted;
  ...
}

Now, in version 1.1 of the app the desire is to change the Challenge object by adding two new @properties.

//v1.1
@interface Challenge : NSObject<NSCoding>
{
  ...
  @property (nonatomic,strong) NSString *challengeId;
  @property (nonatomic,strong) NSString *title;
  @property (nonatomic) BOOL isCompleted;
  ...
  @property (nonatomic) BOOL isActive;
  @property (nonatomic) NSInteger completeCount;
}

My issue is this, when Archiver attempts to decode the Challenge object, because the two new properties exist (e.g. Challenge v1.1 doesn't match copy or alloc pattern for Challenge v1.0) then an exception is thrown.

Essentially, has anyone migrated a NSEncoding object and come accross a similar scenario and what techniques did you employ to overcome the issue?

Options I've considered

So far I've attempted to insert a superclass above Challenge in the object tree but couldn't get it to function.

Also, I've considered using a two step migration, i.e. release v1.1 which decodes persisted challenges in the old format and persists a new type of object NewChallenge in preparation for v1.2 - this seems over-complex to me (considering I just want to add two properties).

I've considered extending Challenge and having the properties exist in a subclass, but this would mean changing a lot of code which is expecting to see a Challenge object.

Any insight would be greatly appreciated.


Solution

  • Many Thanks to Mike Mayo (@greenisus) the original author of Archiver for the clue to what was going on...

    It turns out that during decoding it is possible to check for the existence of any given key. The Archiver project uses the function below to decode objects; so at the point where it could try to decode a non existent key put a check if the coder knows about said key - if it doesn't the skip that iteration of the for loop with a continue clause.

    - (void) autoDecode:(NSCoder *)coder
    {
        NSDictionary *properties = [self properties];
    
        for (NSString *key in properties)
        {
            if (![coder containsValueForKey:key]) {
                continue;
            }
    
            NSString *capitalizedKey = [key stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[key substringToIndex:1] capitalizedString]];
            NSString *selectorString = [NSString stringWithFormat:@"set%@:", capitalizedKey];
            SEL selector = NSSelectorFromString(selectorString);
            NSMethodSignature *signature = [self methodSignatureForSelector:selector];
    
            if (!signature) {
                continue;
            }
    
            ...
    

    In my case the encoded objects also caused a crash when attempting to decode hash and description, hence the requirement for the second continue clause.