7

I have a pretty trivial Swift app that has a model class named DemoNote. An array of DemoNote instances is read/written via keyed archiving. This worked fine while DemoNote was included in the app.

But then I moved DemoNote.swift to a new custom framework called DemoSharedCode. Aside from making sure Xcode was using the framework in the app target, I made sure to

  • Mark DemoNote and its vars and methods as public so they'd be visible outside of the framework
  • Add import DemoSharedCode to any classes that want to use DemoNote

So now the compiler is happy. But at run time the unarchiving fails with this error:

2015-02-17 12:12:53.417 DemoNotesSwift[70800:16504104] *** Terminating app due to 
uncaught exception 'NSInvalidUnarchiveOperationException', reason:
'*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class
(DemoNotesSwift.DemoNote)'

In the above, DemoNotesSwift is the app name, DemoNote is the class name, and the line of code is attempting to unarchive objects from an NSData blob:

let savedObjects = NSKeyedUnarchiver.unarchiveObjectWithData(savedData) as? [(DemoNote)]

I'm guessing that moving DemoNote to the framework means its module name has changed, which breaks unarchiving, but I'm not sure of that. I'm also not sure what to do about it-- maybe I need to call +setClass:forClassName: on the unarchiver, but if so I don't know what the arguments would be.

Tom Harrington
  • 69,312
  • 10
  • 146
  • 170
  • What the archiver does internally is a bit misty. There's an open source project which can help reading archives (I gave up, too much of a nightmare). But it will likely store some class signature. So if you change that under the hood (which is never a good idea) it will (as likely in your case) croak. Just an idea, not based on practical experience. – qwerty_so Feb 17 '15 at 19:28
  • I previously did the same thing in Objective-C (literally, as in I'm doing the same demo project in a different language) and it was OK. It's got to be possible to do the same thing in Swift. – Tom Harrington Feb 17 '15 at 20:59
  • I see. So I guess the Swift framework now differs. Though there's some toll-free bridging between both languages it does not seem to work here. You're probably left to try and error :-/ I guess you already googled for `NSInvalidUnarchiveOperationException` and found the SO answers. – qwerty_so Feb 17 '15 at 21:03

1 Answers1

19

Moving DemoNote from the app to a framework did change the module name, which meant that NSKeyedUnarchiver couldn't find instances of the archived class due to a name mismatch. The fix was to add this line before unarchiving:

NSKeyedUnarchiver.setClass(DemoNote.self, forClassName: "DemoNotesSwift.DemoNote")

In this case, DemoNote.self gets the current full class name, and DemoNotesSwift.DemoNote is what the class used to be called when it was part of the app.

This was only necessary because I had previously existing data that I wanted to keep.

Tom Harrington
  • 69,312
  • 10
  • 146
  • 170
  • Where can I find a tutorial on creating a 100% Swift framework? -- I'm a framework neophyte and have just mastered an ObjC framework for Swift. But I don't know how to collect 'headers'/ObjC within Swift which doesn't have headers. – Frederick C. Lee Jun 26 '15 at 19:50
  • do you happen to know also how to make nskeyedarchiver to archive the class with the correct name the next time it stores it? – João Nunes May 28 '16 at 09:46
  • Normally it isn't a problem, I only had a problem because of my refactoring. If you really need it, `NSKeyedArchiver` has a method that's the reverse of the one I used-- `setClassName(_: forClass:)` – Tom Harrington May 28 '16 at 22:34
  • Although I don't know the details, you can use also `NSKeyedUnarchiver.setClass(DemoNote.classForKeyedUnarchiver(), forClassName: "DemoNotesSwift.DemoNote")`, the method `classForKeyedUnarchiver()` looks more suitable. – David Rysanek Jan 17 '17 at 19:34
  • It looks like that method is intended if you want to override the default class-- that is, specifically if you **don't** want to use `ClassName.self` but want some other class instead. It's useful in some situations but I think it's less appropriate in this case. – Tom Harrington Jan 17 '17 at 23:15
  • This answer won't work for nested objects, check my answer here https://stackoverflow.com/a/46832840/1433612 – Au Ris Oct 19 '17 at 15:39