Refactoring NSCoding to Swift
Rewriting an old rusty Objective-C code is fun.
You start with shiny new classes, adding some optimisation and covering the rest with syntactic sugar.
But some parts can get tricky. Migration is one of them.
While fetching a String from
UserDefaults shouldn’t be a problem.
CoreData has a lightweight migration.
With good old
NSCoding, things might get tricky.
Let’s say we have an Objective-C Class
LCToken that conforms to
Which used to be stored in
(storing sensitive data in
UserDefaults is bad for your security, kids)
How do we create a swift reincarnation without loosing any of the users data?
Let’s swift it up!
We might want to use
Codable protocol in our new class.
Most of the code is auto-generated for us. So the implementation is as simple as adding the protocol conformance.
Codable is also safe, and easy to set up
Let’s create a test to see if we can read the Legacy data.
Codable are not compatible “out of the box”.
A simple solution to migrate a class from a legacy data is to make it
We will need to subclass
NSObject (I know, everything has it’s downsides)
and implement functions required by the protocol.
Now, if data fetching fails, we can try to decode it using
Let’s run the test again…
There is one more thing we forgot to set up. Class names.
NSKeyedArchiver uses a classname as one of the keys for encoding.
Swift class names, though, are prefixed with a module name, so leaving it untouched wouldn’t work.
Luckily there is a
setClass:forClassName: method, which adds something called a class translation mapping for
NSKeyedUnarchiver.setClass(Token.self, forClassName: "LCToken")
Let’s add it in our code and run the test again.
Our users can now update without loosing any of their data.
This example is oversimplified to keep focus on a main problem.
Author recommends not to use UserDefaults in UnitTests, but to have a dependency injected storage and mock it in tests.