Friday, 20 July 2018

Workaround for serializing Codable fragments

TL;DR - Wrap the Codable type in an array and use a JSONDecoder to convert it to Data

Serialization of data has been immensely improved by the introduction of Codable. This interface provides an api with much less boilerplate than the classic NSCoding.
Currently Swift provides decoders and encoders for 2 types of data, JSON and property list:
One of the limitations is that neither of them support single values, also known as fragments. For example:
let json = "A string".data(using: .utf8)!
do {
    try JSONDecoder().decode(String.self, from: json)
} catch {
    // Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start
    // with array or object and option to allow fragments not set."
    // UserInfo={NSDebugDescription=JSON text did not start with array
    // or object and option to allow fragments not set.}
}
The issue related to JSON is raised on the Swift JIRA, SR-6163. The reason this happens is because JSONDecoder is implemented using JSONSerialization but it doesn't provide an option to allow fragments which is where the error comes from.

Real life example

A popular tool for persistency in Swift is Disk. One of it's features is the ability to save a Codable type to disk:
public extension Disk {
    static func save<T: Encodable>(_ value: T, to directory: Directory, as path: String) throws {
        // Implementation using JSONEncoder
    }
}
The library is great but it's limited by the Swift issue, if we try to save a string it'll fail:
try Disk.save("A string", to: .documents, as: "filename.extension") // fails
No compiler errors are given because String is Encodable but this will throw the following error:
Error Domain=NSCocoaErrorDomain Code=4866 "Top-level String encoded as string JSON fragment." UserInfo={NSCodingPath=( ), NSDebugDescription=Top-level String encoded as string JSON fragment.}
Both the API and the documentation (Disk currently supports persistence of the following types: Codable, ...) suggest this should work. However, since Disk is implemented using JSONEncoder for serialization it doesn't.
Note that I used Disk as an example because it's well documented, has good tests and works very nicely. The fact that I'm pointing a bug doesn't mean it's anything other than great.

One possible (and temporary) solution to this problem

Since Swift does not support fragments a reliable and somewhat questionable implementation is to wrap the Encodabletype into an array to guarantee it'll encode:
private extension Encodable {
    func encode() -> Data? {
        return try? JSONEncoder().encode([self])
    }
}
This serialized Data can then be stored into the disk or sent somewhere.
To retrieve the original Codable type we can revert the process. We decode the data, take the first item in the array and cast it to the corresponding type. Note that this returns a generic parameter because there is no easy way to encode the original type.
extension Data {
    func decode<T: Decodable>() -> T? {
        return (try? JSONDecoder().decode([T].self, from: self))?.first
    }
}
Going back to the original example from SR-6163, these extensions can be used like this:
let jsonPrimitive = "A string"
let encodedData = jsonPrimitive.encode()
let decodedValue: String? = encodedData?.decode() // "A string"

Conclusions

The proposed workaround is by no means elegant or space efficient but it reliably converts any Codable type to Dataand back to Codable. Therefore, this is good tool to have in your toolbelt until swift provides a decoder/encoder that allows fragments as Codable provides a very convenient way of serializing objects.

I’d like to thank Nahuel MarisiDaniel Haight and Neil Horton for reviewing this article.

Tuesday, 27 March 2018

Caveats of Swift default protocol extensions

TL;DR - Think carefully whether or not you need to add a default implementation to a property defined on a protocol -
Be prepared to avoid wasting a day of painful debugging caused by an obscure scenario in Swift! Swift 2 introduced the ability to add extensions to protocols, for example: 
protocol SomeProtocol {
    var property: String { get }
}

extension SomeProtocol {
    var property: String {
        return "Default value"
    }
}
This is a great language feature but it introduces the potential for subtle bugs when modifying the protocol. In this post I'll cover 2 cases: renaming a property, and changing the type of a property.

Case 1: Renaming a property defined in a protocol

Protocols can define properties to be implemented by the conforming type. For example, an http request can be modelled by the following:
enum HTTPMethod {
    case get
    case post
}


protocol HTTPRequest {
    var httpMethod: HTTPMethod { get }
}
In the majority of cases the http method used by iOS apps will be a GET request, so it's sensible to define that all http requests default to .get:
extension HTTPRequest {
    var httpMethod: HTTPMethod {
        return .get
    }
}
A POST http request could be defined like this:
struct ExampleHTTPRequest: HTTPRequest {
    var httpMethod: HTTPMethod = .post
}
An HTTPClient can then make http requests using the httpMethod defined in the HTTPRequest protocol:
struct HTTPClient {
    func make(httpRequest: HTTPRequest) {
        // some implementation using the http method
    }
}

let client = HTTPClient()
client.make(httpRequest: ExampleHTTPRequest()) -> uses a POST
Imagine that some time later the httpMethod gets renamed to method:
protocol HTTPRequest {
    var method: HTTPMethod { get }
}

extension HTTPRequest {
    var method: HTTPMethod {
        return .get
    }
}
After this change, when the http client makes the HTTPRequest for the original ExampleHTTPRequest the HTTPMethod is now get because it is using the default implementation of HTTPRequest defined in it's extension:
let client = HTTPClient()
client.make(httpRequest: ExampleHTTPRequest()) -> uses a GET!
Note that the ExampleHTTPRequest didn't change but the behaviour when used in the HTTPClient is different. The ExampleHTTPRequest still defines var httpMethod: HTTPMethod = .post making it hard and confusing to debug.

Case 2: Changing the type of a property defined in a protocol

In this second case instead of renaming the property we'll change the type. Let's start by telling the story of JohnDoe and JaneDoe, 2 devoted athletes. The first thing they learnt as a Person was to Walk:
protocol Walking {}

protocol Person {
    var moveAction: Walking? { get }
}

struct Walk: Walking {}
Since not every Person can walk, the moveAction is optional with a nil default:
extension Person {
    var moveAction: Walking? {
        return nil
    }
}
Once JohnDoe and JaneDoe learned how to Walk they looked like this:
struct JohnDoe: Person {
    var moveAction: Walking? = Walk()
}

struct JaneDoe: Person {
    var moveAction: Walking? = Walk()
}
After learning how to Walk they figured it was time to have a Race:
struct Race {
    var people: [Person]

    func start() {
        people.forEach { person in
            // use person's moveAction
        }
    }
}

let firstRace = Race(people: [JohnDoe(), JaneDoe()])
firstRace.start()
Both of them managed to Walk in this Race:
firstRace.people[0].moveAction // returns Walk
firstRace.people[1].moveAction // returns Walk
After JohnDoe won the Race by a great distance, the next step was to learn how to run:
protocol Running {}
Person could now run:
protocol Person {
    var moveAction: Running? { get }
}

extension Person {
    var moveAction: Running? {
        return nil
    }
}
Lazy JohnDoe was confident that his Walk was good enough to win the next Race and didn't learn how to Run. However, JaneDoe who was much more clever than JohnDoe (and knew a thing or two about Swift) updated her moveAction to include Running so she could Run:
struct JaneDoe: Person {
    var moveAction: Running? = Run()
}
The 2nd Race was on! The whistle went and the race started:
let secondRace = Race(people: [JohnDoe(), JaneDoe()])
secondRace.start()
To JohnDoes surprise he had suddenly forgotten how to Walk as a Person whereas JaneDoe Run like the wind:
secondRace.people[0].moveAction // returns nil for JohnDoe
secondRace.people[1].moveAction // returns Run for JaneDoe
JohnDoe couldn't move and lost the race without knowing what hit him. There wasn't even a compiler error/warning to tell him what happened. Some people have gone as far as saying he couldn't take the pressure and choked...
However, a careful inspection of the Swift code tells us that poor JohnDoe was stripped of his ability to Walk as a Person when the Person protocol's moveAction was modified to Running. This is due to the fact that our default extension is now returning nil for JohnDoe when he is considered a Person. When accessing a property the reference type of the variable matters. When the instance of JohnDoe is considered a Person, the moveAction property from the Person default extension is used, i.e. Running?. Whereas when the instance is refered as JohnDoe the property used is the one defined by JohnDoe, i.e. Walking?:
(secondRace.people[0] as Person).moveAction // returns nil
(secondRace.people[0] as! JohnDoe).moveAction // returns Walk
The type for moveAction defined by JohnDoe is Walking? compared to Running? for Person. This subtle changes means JohnDoe no longer can Walk as Person

Conclusions

If you're thinking that deprecating the old property is the way to go, it does not work. Deprecations don't have any effect whatsoever on the conforming type as that type can define it's own properties overriding the protocol ones:
protocol ExampleProtocol {
    @available(*, deprecated, renamed: "newProperty")
    var oldProperty: String { get }

    var newProperty: String { get }
}

extension ExampleProtocol {
    @available(*, deprecated, renamed: "newProperty")
    var oldProperty: String {
        return "Default value"
    }

    var newProperty: String {
        return "Default value"
    }
}
The following ExampleStruct has no warning or errors:
struct ExampleStruct: ExampleProtocol {
    var oldProperty: String
}
Neither changing it's type produces an error/warning:
struct ExampleStruct: ExampleProtocol {
    var oldProperty: Int
}
Therefore, I suggest that you think carefully when adding default implementations in extensions. If you absolutely must change a property that has default defined I suggest 2 approaches: 
  1. Have complete confidence that you can propagate the change to every consuming type of your protocol
  2. Remove the default implementation to force all consumers to implement it
This is specially important when modifying public interfaces since it's likely that you'll have no visibility of the projects that make use of that protocol. All in all, these solutions are not great and it's probably best to avoid having public extensions adding defaults altogether.
I’d like to thank Nahuel MarisiDaniel Haight and Neil Horton for reviewing this article.