Unarchiving an NSMutableDictionary archived from Objective-C

I am converting my app from Objective-C to Swift and I am having a problem unarchiving (in Swift) my persist objects that were previously archived in Objective-C. For example, I have an NSMutableDictionary file that was archived with the following Objective-C code:

NSString *indexFilename = "somefilename";

NSMutableDictionary *index = [NSMutableDictionary dictionary];

// Dictionary gets populated...

// Save it...

[NSKeyedArchiver archiveRootObject:index toFile:indexFilename];

These files are unarchived just fine in the Objective-C version of the app with:

NSMutableDictionary *index = nil;

if ([[NSFileManager defaultManager] fileExistsAtPath:indexFilename]) {

index = [NSKeyedUnarchiver unarchiveObjectWithFile:indexFilename];

}

But in the new Swift version, the following code sets index to nil:

let filename = "somefilename"

var index = NSKeyedUnarchiver.unarchiveObjectWithFile(filename)

Should this just work or is there something else I need to do to unarchive these files successfully in Swift?

Thanks.

Answered by DTS Engineer in 120971022

I hacked together a test project to see if I could reproduce your problem. Here’s my Objective-C side.

+ (void)hackPath:(NSString *)indexFilename {
    NSMutableDictionary *index = [NSMutableDictionary dictionary];
    index[@"a"] = @"A";
    index[@"b"] = @"B";
    BOOL success = [NSKeyedArchiver archiveRootObject:index toFile:indexFilename];
    assert(success);
}

And here’s the Swift side.

let docDir = try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)
let docName = "hack-\(NSUUID().UUIDString).dat"
let docFile = docDir.URLByAppendingPathComponent(docName)

print("docName = \(docName)")

Hack.hackPath(docFile.path)

let index = NSKeyedUnarchiver.unarchiveObjectWithFile(docFile.path!)
print(index)

This prints:

docName = hack-7531C650-90CC-4196-A957-796B4C8FE639.dat
Optional({
    a = A;
    b = B;
})

indicating that Swift was able to decode the archive.

IIRC NSKeyedUnarchiver lets you allocate an instance and set a delegate on it to learn about and potentially override various events in the archiving process. That’s the way I usually debug problems like this.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

1. "somefilename" is an absolute path, or just a file name?


2. Are you sure there's no error message about the decoding? Do you have exception breakpoints set for Obj-C exceptions?


3. With the legacy-style decoding you're doing, if decoding of an object fails, there's little indication of what went wrong, other than something is nil. If your dictionary has objects of classes with custom decoding (initWithCoder), you can try setting breakpoints there to watch what happens. If they're all standard classes with built-in decoding, then perhaps you could try using an unarchiver delegate to get notified when decoding fails.

Thank you for the reply.


1. "somefilename" is absolute. It resolves to a file in the Documents folder that successfully is unarchived in Obj-C.

2. I'm sure that there are no exceptions being thrown. I have enabled Obj-C Exceptions.

3. My archived dictionary just contains strings, so I don't see any way to debug the creation of those. But that's a good thought.


I'm going to test creating, populating, and archiving a similar dictionary in Swift to make sure my unarchive code is correct. I also read that the @objc attribute is required for unarchiving ObjC classes but this is not a custom class, just a built in NSMutableDictionary, so I'm not sure if that applies in my case.

Accepted Answer

I hacked together a test project to see if I could reproduce your problem. Here’s my Objective-C side.

+ (void)hackPath:(NSString *)indexFilename {
    NSMutableDictionary *index = [NSMutableDictionary dictionary];
    index[@"a"] = @"A";
    index[@"b"] = @"B";
    BOOL success = [NSKeyedArchiver archiveRootObject:index toFile:indexFilename];
    assert(success);
}

And here’s the Swift side.

let docDir = try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)
let docName = "hack-\(NSUUID().UUIDString).dat"
let docFile = docDir.URLByAppendingPathComponent(docName)

print("docName = \(docName)")

Hack.hackPath(docFile.path)

let index = NSKeyedUnarchiver.unarchiveObjectWithFile(docFile.path!)
print(index)

This prints:

docName = hack-7531C650-90CC-4196-A957-796B4C8FE639.dat
Optional({
    a = A;
    b = B;
})

indicating that Swift was able to decode the archive.

IIRC NSKeyedUnarchiver lets you allocate an instance and set a delegate on it to learn about and potentially override various events in the archiving process. That’s the way I usually debug problems like this.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks eskimo.


I see my error. I used absoluteString instead of path for the NSURL. Both return a string but one works and the other just makes the call not work properly.


Here's is what I had:


let index = NSKeyedUnarchiver.unarchiveObjectWithFile(docFile.absoluteString)


If you use absoluteString, unarchiveObjectWithFile returns nil without any error indication.


Joe

Unarchiving an NSMutableDictionary archived from Objective-C
 
 
Q