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.
And 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 NSCoding

Which used to be stored in UserDefaults 
(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.

Persistence with Codable is also safe, and easy to set up

Testing

Let’s create a test to see if we can read the Legacy data.

XCTAssertTrue failed

Unfortunately, NSCoding and Codable are not compatible “out of the box”.

Migration

A simple solution to migrate a class from a legacy data is to make it NSCoding conformant.

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 NSCoding.

Let’s run the test again…

Still fails

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 asetClass:forClassName: method, which adds something called a class translation mapping for NSKeyedUnarchiver

NSKeyedUnarchiver.setClass(Token.self, forClassName: "LCToken")

Let’s add it in our code and run the test again.

It’s green!

It’s Green!

Our users can now update without loosing any of their data. 
Migrate safely!

Disclamer

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.