Swift: Type Safety, Encoding & Decoding

Taking a closer look at one of the most popular programming languages in the world.
Pere Daniel Prieto
Pere Daniel Prieto
June 20, 2019

Since its unveiling at Apple’s 2014 WWDC keynote, Swift has been gradually adopted by the developer community as the default programming language of choice when it comes to implementing software on Apple devices. I’m going to be focusing on one of its most useful features, type safety.

Swift: A Condensed History

Following Swift’s introduction in 2014, Apple decided to replace the proprietary language approach with the open source approach for the 2.2 version which was released in December of the following year.

This decision drew the attention of programmers and encouraged them to explore native iOS development in more detail, thus expanding upon the community of developers that were contributing to the App Store at the time.

Prior to the release of Swift 4.0 in 2018, however, Swift wasn’t the easiest language to use when it came to implementing backend communication on native apps. Swift 4.0 rectified that and, since then, Swift has continued to grow in popularity. It’s the programming language of choice for the majority of iOS developers these days – almost completely replacing Objective-C on framework implementation. Very few apps developed following the open source update in 2015 have been implemented using Obj-C.

In my opinion, unless you have a really good reason to use a different programming language, you should opt for Swift.

For those of us that started out developing using Obj-C, migrating to Swift was a blessing. It promised readable modern syntax, dropped those damn brackets and came with helpful features like type safety and access-level keys.

Let’s look at the former in more detail…

Type Safety: the good vs. the bad.

Type safety discourages and prevents users from making mistakes in their code whilst working in Swift. Without type safety, it’s easy to make mistakes and create code that is unsafe. Swift, using the type safety feature, for example, insists that users ensure an object is a String before accessing the uppercased()function. If they don’t, the compiler triggers an error.

A pretty straightforward and desirable feature for any programming language, right?!

Yes, for the most part.

The one thing that stood in the way of its usability, prior to the release of Swift 4.0, was parsing network responses in the JSON format. Network connectivity is essential. The issue with parsing a network response in the JSON format was that the function jsonObject(with:options:) of the JSONSerialization class returned an instance of Any e.g an object without a specified type.

As a result, parsing the response to an object, whether native to Swift or custom, became a nightmare requiring downcast after downcast to assign values to properties with specific types – which was essentially 99.9 % of them!

As an example, let’s look at the following pair of simple structs:

<p> CODE: "https://gist.github.com/peredaniel/c41cf2705b031c9fc972b6b1f6a4a6a2.js"</p>

A JSON representing a single landmark object would have the following format:

<p> CODE: "https://gist.github.com/peredaniel/f38c32da91947292f4e2dfdea502dc2b.js"</p>

The code to create a new landmark object from the previous response, using the object returned by jsonObject(with:options:) of the JSONSerialization class, looks similar to the following:This example is pretty simple since both properties in the Coordinate are of the Double type and there are just five properties to parse in total.

<p> CODE: "https://gist.github.com/peredaniel/a4c7eaa595dc1c41b8d68a6b5c9ad806.js"</p>

This example does, however, demonstrate the following:

  • We need to introduce a significant amount of boilerplate code in the form of downcasts for a large data model – either by using guard … else { } statements, conditional bindings or default values for optionals.
  • We must hardcode the keys when accessing its values in the dictionary, thus causing it to be prone to errors. Moreover, any change on the keys is tedious since it may be unclear as to which part of the initialization function they are located.
  • The initializer is JSON-specific which means that, in the case of changing the source of the data, we may need to implement a different initializer.

The solution: Codable!

With the release of Swift 4.0 in 2018, Apple introduced a couple of new protocols: Decodable and Encodable to help make life easier. Codable, according to the Apple Developer website, is a ‘type alias’ for the protocols. ‘When you use Codableas a type or a generic constraint, it matches any type that conforms to both protocols’.

Most Swift native types do already conform to both.

As the name suggests, an object conforming to Decodable may be initialized using binary data by means of a mapper object known as a Decoder.

Similarly, an object conforming to Encodable may be converted to binary data by means of a similar mapper object known as an Encoder.

One of the most intriguing things about these protocols is that the implementation is inferred by the compiler in the same way as the default initializer is for structs. If all of the properties defined in your object type do already conform to Codable, you only need to declare conformance to the protocol in the type declaration. The compiler will automatically add the necessary functions when it’s time to build.

Following the previous example, to make our landmark type conform to Codable, the only changes required are the following:

<p> CODE: "https://gist.github.com/peredaniel/868a2a6e6a1819d1a75e00a88aadb47a.js"</p>

And that’s it!

Since Double does conform to Codable, we only need to declare the conformance in Coordinate to make it Codable. And since Coordinate is now Codable, so are all of the properties in landmark – which makes it Codable just by declaring the conformance.

Got that?! Easier than it sounds, right?! This is just the tip of the iceberg!

Check out the advice on the Apple Developer website for more tips on encoding and decoding custom types.

The advantages of this approach:

  • In the simpler cases, it removes a lot of boilerplate code since we don’t need to implement the initializers.
  • It enables a great degree of customization if implementing the initializer.
  • In the case of needing to explicitly define the map to the keys being decoded, we encapsulate all of the hardcoded values in a single place which makes them easier to spot and change.
  • The initializer and the encoding function are completely agnostic to the origin or destination of the data. The same initializer works for any type of data format, whether it’s in JSON, plist or something else entirely. The function parameter is either an instance of a Decoder or Encoder and thus that object is responsible for parsing the response into the appropriate object type.

By default, Swift 4.0 includes two Coders: JSON and PropertyList.

If you require a coder for a different type of data format, you may need to implement it yourself or browse the internet for further assistance.

Oleksii Dykan has an Open Source CodableFirebase library to make Codable work with Firebase snapshot responses, for example.

Going further: making anything Codable!

One of the few drawbacks when using Codable is, again, type safety. The properties in your Codable objects must be Codable themselves, by being either Swift native or a custom type that conforms to the protocol. There are thus some limitations on what you can encode or decode.

Of course, the vast majority of Codable implementations won’t be affected by it since type safety is a feature that prevents lots of bugs. Nevertheless, there may be a few instances in which you need to decode a property with type [String: Any]or [Any] – particularly the dictionary if your backend colleague needs to send custom parameters in the response.

In these cases, Decodable is useless since Any is obviously not Decodable. So, is there any way to work around this limitation?

The answer is, annoyingly, both yes and no.  

Since Swift is type safe, you can’t decode an object that’s not conforming to Decodable using a Decoder. If you try, you’ll get a compiler error and there’s no way to work around this.

Nevertheless, what we can do is implement the decoding functions for these particular cases before delegating to the standard decoding functions for types which are already Decodable.

A quick search on the internet lead me to the following file:

<p> CODE: "https://gist.github.com/peredaniel/1a56a21c83cc19a39424105a83249960.js"</p>

This is a slightly modified version of the original file. We extend the keyed and unkeyed containers for both encoding and decoding in order to implement the functions enabling these features in both [String: Any] and [Any] types.

Of course, this implementation is not exhaustive. There are many types that aren’t being considered. The implementation can be improved by splitting the file per functionality, either by encoding or decoding or perhaps packaging everything into an open source framework. But, for now, it meets our needs and it’s as easy to use as it seems!

Download the file, drag it into your project and… voilà! You can now use Codablewith arrays and dictionaries not explicitly declared as Codable too.

A word of advice…

Just a word of advice if you intend to use this file – you could, in theory, use it to parse the whole response into a [String: Any]. Don’t do this!

First of all, as mentioned, the implementation is not exhaustive so there may be types that are returned incorrectly – thus failing the parse process.

Secondly, why would you want to do so? It would be far easier to use the function jsonObject(with:options:) of the JSONSerialization class before then casting the returned Any instance to [String: Any].

Furthermore, you’d have to return to square one which makes the Codableconformance and the usage of this file completely useless.

In short, follow my advice and you should be fine!

In conclusion

Don’t be afraid of adding features to Swift.

It’s a great programming language but it does and must evolve around the community and your individual needs as a developer. That is, after all, what open source is all about!

Don’t hesitate to leave comments in the shared file if you have any ideas for improvements that you think should be made. I’m always open to suggestions, comments and questions!

---

Are you a fan of Swift? What’s your programming language of choice? Tweet us and we’ll be sure to retweet the responses!

We Are Mobile First is a digital product agency based in Barcelona helping to transform businesses in a mobile-first world. Follow us on Twitter, LinkedIn and Medium to be notified of our future posts and stay up-to-date with our company news.

We share weekly content on everything from our Apple WWDC 2019 Highlights to how to welcome new developers to the team.

(image credit: Apple Developer)