Swift

Decoding Codable Code

I’ve been working on a recent project where I’ve been interacting with remote data. This data is delivered in JSON format. JSON (or its Swift equivalent Dictionary<String, Any>) are not exactly type safe. Seeing as type safety is a key feature of Swift, it make sense to translate this data into classes or structures. This post discusses how to do this.

The Data

For our example lets suppose we have an API that provides a list of albums and songs by Weird Al Yankovich. For convenience, instead of setting up an actual API endpoint to call I’ve taken a small sample and translated it into a JSON file. Download this and add it into your project. This is what the raw JSON looks like:

 [
  {
  "release_date":487915200,
  "title":"Dare to Be Stupid",
  "tracks":[
            {
            "length":212,
            "parody_of":"\"Like a Virgin\" by Madonna",
            "sequence":1,
            "title":"\"Like a Surgeon\"",
            "writers":"William Steinberg, Thomas Kelly, Alfred Yankovic"
            },
            {
            "length":205,
            "parody_of":"Style parody of Devo",
            "sequence":2,
            "title":"\"Dare to Be Stupid\"",
            "writers":"Yankovic"
            },
            {
            "length":184,
            "parody_of":"\"I Want a New Drug\" by Huey Lewis and the News",
            "sequence":3,
            "title":"\"I Want a New Duck\"",
            "writers":"Christopher Hayes, Hugh Cregg III, Yankovic"
            },
            {
            "length":244,
            "parody_of":"Style parody of Elvis Presley-like Doo-wop",
            "sequence":4,
            "title":"\"One More Minute\"",
            "writers":"Yankovic"
            },
            {
            "length":238,
            "parody_of":"\"Lola\" by The Kinks",
            "sequence":5,
            "title":"\"Yoda\"",
            "writers":"Raymond Davies, Yankovic"
            },
            {
            "length":65,
            "parody_of":"Cover of titular television theme",
            "sequence":6,
            "title":"\"George of the Jungle\"",
            "writers":"Stan Worth, Sheldon Allman"
            },
            {
            "length":263,
            "parody_of":"Style parody of 1950s sci-fi soundtracks",
            "sequence":7,
            "title":"\"Slime Creatures from Outer Space\"",
            "writers":"Yankovic"
            },
            {
            "length":288,
            "parody_of":"\"Girls Just Want to Have Fun\" by Cyndi Lauper",
            "sequence":8,
            "title":"\"Girls Just Want to Have Lunch\"",
            "writers":"Robert Hazard, Yankovic"
            },
            {
            "length":186,
            "parody_of":"Style parody of 1920s and 1930s music",
            "sequence":9,
            "title":"\"This Is the Life\"",
            "writers":"Yankovic"
            },
            {
            "length":198,
            "parody_of":"Original",
            "sequence":10,
            "title":"\"Cable TV\"",
            "writers":"Yankovic"
            },
            {
            "length":233,
            "parody_of":"A polka medley including:",
            "sequence":11,
            "title":"\"Hooked on Polkas\"",
            "writers":""
            }
            ]
  }
  ]

If you are skilled at reading JSON, you should see that this is an array of albums. Each album is represented by a dictionary with keys release_date, title, and tracks. These keys have types of date, string and array of dictionaries respectively. Watch track dictionary contains keys length, parody_of, sequence, title, and writers.

We access the JSON file’s information like so:

let path = Bundle.main.path(forResource: "WierdAlAlbums", ofType: "json")!
let data = NSData.init(contentsOfFile: path)! as Data
let albums = try! JSONSerialization.jsonObject(with: data, options: []) as! [[String:Any]]

albums is an array of dictionaries. If we want to get an album, and read its title we would need to do some unwrapping and casting:

let firstAlbumName = ((albums.first!)["title"] as! String

This should make any Swift developer cringe (and perhaps vomit in their mouth. ??) We are accessing the dictionary data with hardcoded strings (easy to mistype, hard to remember, etc) then force unwrapping the value. Barf! Additionally, each time we need to access the title, we need to do it all over again. That doesn’t seem very DRY. All in all, this is sub optimal way to handle this information. Lets see what we can do to clean this up.

The first and most important step to cleaning up and simplifying this mess is to create our own types that represent the data. This approach provides several benefits, with type safety and DRY code at the top of the list. Lets get started. We can represent our albums and track data with the following structs:

struct Album {
    // 1
    enum DictionaryKey:String{
        case title, release_date, tracks
    }
    // 2
    let title:String
    let release_date:Date
    let tracks:[Track]
    // 3
    init(from dict:[String:Any]){
        title = dict[DictionaryKey.title.rawValue] as! String
        // 3.1
        let timeInterval = dict[DictionaryKey.release_date.rawValue] as! TimeInterval
        release_date = Date.init(timeIntervalSince1970: timeInterval)
        // 3.2
        let trackData = dict[DictionaryKey.tracks.rawValue] as! [[String:Any]]
        tracks = trackData.map({ (data) -> Track in
            return Track(from: data)
        })
    }
}

struct Track{
    
    enum DictionaryKey:String{
        case length, parody_of, sequence, title, writers
    }
    
    let length:Int
    let parody_of:String
    let sequence:Int
    let title:String
    let writers:String
    
    init(from dict:[String:Any]){
        length = dict[DictionaryKey.length.rawValue] as! Int
        parody_of = dict[DictionaryKey.parody_of.rawValue] as! String
        title = dict[DictionaryKey.title.rawValue] as! String
        writers = dict[DictionaryKey.writers.rawValue] as! String
        sequence = dict[DictionaryKey.sequence.rawValue] as! Int
    }
}

Lets go over what is happening here. First we have declared two structs, Album and Track. Their internal structure is very similar, so we will just review the key points of Album:

  1. We have an enum that has a case for each key in the dictionary for the data that represents the Album. Since we have declared the enum to be of type string, we can use its raw value to access the value in the dictionary. We do this to keep our code DRY (anytime we need to access a key of a dictionary representing a dictionary we use these keys). Secondly, we avoid misspelling by using an enum case.
  2. We have struct variables that represent a value for each key in the album dictionary.
  3. We have created an initializer that takes a dictionary and use the previous two steps to map the data from the dictionary to the correct struct variable.
    1. Since JSON doesn’t store date information directly, we translate from the time interval to the date we need. In some cases this may be a string translation, and a DateFormatter will be needed.
    2. Since the track data is an array of dictionaries we need to map the dictionaries to Tracks. We can do this either in a for loop, or via the map function (as shown above).

With these steps we can quickly and efficiently map or data from dictionaries to value types that provide type safety when accessing its information. No more forced unwrapping every time we want to use an album’s title, or a tracks sequence. While this solution is great it can be improved!

While the above code is great, we need to translate our array of dictionaries created from the JSONSerialization into these structs:

let data = NSData.init(contentsOfFile: path)! as Data
let albumsData = try! JSONSerialization.jsonObject(with: data, options: []) as! [[String:Any]]
let albums = albumsData.map{return Album(from:$0)} // albums is of type [Album]

Now that our data has been translated into these structs, we are able to access information safely and efficiently.

While the above code is good, I can’t help but wonder if there is a better way. We take data (Data type), transform it into another kind of data ([[String:Any]]) then translate that into yet another type of data ([Album]). Is there anyway we can skip the middle step and go from Data to [Album]?

Codable

Da-da-da-da! The Codable protocol is perfect for this situation.  If something conforms to the Codable protocol, then it is able to convert itself into and out of an external representation. I our case, we are converting from a JSON data object to our structs. (Its good to note that Codable is a type alias that conforms to both the Decodable and Encodable protocols.) To explore our use of the Codable protocol, let us examine how it changes our struct representation:

struct Album:Codable{
    let title:String
    let release_date:Date
    let tracks:[Track]
}

struct Track:Codable{
    let length:Int
    let parody_of:String
    let sequence:Int
    let title:String
    let writers:String
}

Hmmmm… That simplified things quite a bit. All we are left with are the struct variables. ‘How do we use this?’ you may ask.  I’ll show you:

let albums = try! decoder.decode([Album].self, from: data )// albums is of type [Album]

Boosh! Thats sweet! I love when things simplify. Its good to note that this works so well because our struct variable names are the same as the JSON keys. Hence the awkward release_date and parody_of variable names. They don’t exactly match the lower camel case structure that swift uses. Luckily for us, there is a way to fix this. First lets see how our struct code changes:

struct Album:Codable{
    let title:String
    let releaseDate:Date
    let tracks:[Track]
    
    // 1
    enum CodingKeys: String, CodingKey{
        // 2
        case title, tracks
        // 3
        case releaseDate = "release_date"
    } 
}

struct Track:Codable{
       
    let length:Int
    let parodyOf:String
    let sequence:Int
    let title:String
    let writers:String

    enum CodingKeys: String, CodingKey{ 
        case parodyOf = "parody_of"
        case length, sequence, title, writers 
    }
}

The only difference between the code above, and our earlier definition is the addition of the CodingKeys enum. Lets go over each part of this enum:

  1. The enum is titled CodingKeys, has a String raw type and conforms to the CodingKey protocol. This protocol indicates that these enum cases may be use for encoding and decoding. Each case in our enum represents a variable name in the struct.
  2. We must list any of the struct’s variable names we want to encode/decode. So we first list the ones that match our JSON keys, just to get them out of the way.
  3. Since our struct variable name releaseDate is different from the JSON key representing that data, we must map the raw value to the enum case, thus releaseDate.rawValue = "release_date".

Running our code works great! We can now map our JSON information to our structs while also adhering to Swift API design guidelines. Yay! Except that I’m lying. While the code runs, and mostly works, we still have a problem. Remember how we needed to translate the date data using the timeIntervalSince initializer (3.1 above)? Yeah, that still needs to happen, but its not. While the above code compiles, runs, and returns an array of albums, a closer inspection of the return shows that something is not quite right:

print(albums.first!.releaseDate)// prints 2016-06-18 04:00:00 +0000

Yup, I’m fairly sure Dare to Be Stupid came out in 1985, not 2016… We have several solutions we could use to solve this. First, we could store the timeInterval data as a private variable and provide releaseDate as a computed variable:

struct Album:Codable{
    let title:String
    var releaseDate:Date{
        return Date(timeIntervalSince1970: releaseDateTimeInterval)
    }

    let tracks:[Track]
    private let releaseDateTimeInterval:TimeInterval

    enum CodingKeys: String, CodingKey{
        case releaseDateTimeInterval = "release_date"
        case title, tracks
    }
    
}

This works. However, I’m not sure I like the storage of data I don’t really need or want. Our other option is to write our own custom decoder init function:

struct Album:Codable{
    let title:String
    var releaseDate:Date
    let tracks:[Track]

    enum CodingKeys: String, CodingKey{
        case releaseDate = "release_date"
        case title, tracks
    }
    
    // 1
    init(from decoder: Decoder) throws {
        // 2
        let values = try decoder.container(keyedBy: CodingKeys.self)
        // 3
        title = try values.decode(String.self, forKey: .title)
        // 4
        let timeInterval = try values.decode(Double.self, forKey: .releaseDate)
        releaseDate = Date.init(timeIntervalSince1970: timeInterval)
        // 5
        tracks = try values.decode([Track].self, forKey: .tracks)
    }
}

Lets go over this initializer:

  1. The first thing to note is that this is overriding the default initializer provided by conforming to the decodable protocol.
  2. The first thing we need to do, its to start accessing the information inside the decoder. We do this by calling the container method. This returns a keyed decoding container, that uses the keys we passed in (CodingKeys)
  3. We use the values decoding container to access the information we need. In this case we are getting the title information. We do so by calling the decode method and passing in the type we are expecting and the key we are trying to access. In this case we are expecting a String, for the key .title.
  4. This step is why we are writing this custom initializer. We recognize that the key releaseDate doesn’t contain the date that we need, but rather the time interval needed to translate to the date we need. So we decode the data into a double, then in the next line convert that into the date we need and initialize the releaseDate variable.
  5. Finally, it is worth noting that the decode method can decode into any type that conforms to the Decodable protocol, also that the built in types of Array, Dictionary, and Optional all conform to Codable (and hence Decodable) if they contain codable types. So, in this case since Track is codable, then [Track] is also codable, and we can use the decode method to decode the data for the tracks key into our tracks array!

If you print the album title again, it yields:

print(albums.first!.releaseDate)// prints 1985-06-18 04:00:00 +0000

Which is, of course, what we were all expecting. While we have only chosen to override the init function, if you need to subsequently encode this data (perhaps a user is allowed to alter it and send it back up) then you would also need to implement your own custom encode method.

TL;DR

Conform your classes and structs to the Codable protocol for easy translation of JSON (or any other codable type) to the type of object that you are using!

You can customize your implementation to better conform to proper Swift API design and translate data into different form upon encoding and decoding.

Sources

https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types

https://developer.apple.com/documentation/swift/codable

https://developer.apple.com/documentation/swift/encodable

https://developer.apple.com/documentation/swift/decodable

https://www.objc.io/blog/2018/02/06/networking-with-codable/

https://developer.apple.com/documentation/foundation/archives_and_serialization/using_json_with_custom_types

Frameworks, Swift

Proper Presentations

Aristotle once said:

The aim of art is to represent not the outward appearance of things, but their inward significance.

Such is the aim of our UI’s. A good UI helps users navigate the utility that the app provides them. It enhances the experience without distracting from the purpose of the application.

As developers, our job is to aid designers in delivering this experience. UIKit provides a lot of tools we can use to build our applications. I would like to discuss a UIKit protocol that has made my life as a developer so much easier: UIAppearance

UIAppearance inherits from NSObjectProtocol and its job is to customize the appearance of instances of any class that adopts the protocol. Whats great about this is that it is adopted by UIBarItem and UIView. This means that you can customize the appearance of any subclass of UIView and UIBarItem

So how do we use this? Lets start with a simple example. Suppose you are developing an application that utilizes navigation controllers. In fact lets say that there are multiple views, each with their own navigation controllers. Your designer has determined that they would like the navigation bar to be TARDIS blue. There are several solutions to this problem. A common one that I have seen developers use is to subclass UINavigationController and override its viewDidLoad method to set the navigation bar color there like so:

class MyNavigationViewController: UINavigationController {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.navigationBar.barTintColor = UIColor.tardisBlue
    }
}

This works, but it requires you to use this subclass for every instance of UINavigationController and only alters the navigation bar within the scope of this particular navigation controller. This seems inefficient (and indeed it is!).

Because UINavigationBar inherits from UIView, it conforms to the UIAppearance protocol and we can customize the appearance of every UINavigationBar outside of any particular scope. We can do this with the following line of code:

UINavigationBar.appearance().barTintColor = UIColor.tardisBlue

That one line of code sets the tin color for all navigation bars in your application. One thing to note is that these appearance changes are applied as a view enters a view. If you change an appearance while a view is displayed, nothing will happen. You will need to remove the view from the view hierarchy and then put it back. With this in mind, I recommend setting all your custom appearances in the app delegate didFinishLaunchingWithOptions method.

I personally like to create a struct called Appearance that has a single static function that sets all the appearances in my applications:

import UIKit

struct Appearances{
    static func set(){
        UINavigationBar.appearance().barTintColor = UIColor.tardisBlue
        UINavigationBar.appearance().tintColor = UIColor.white
        UINavigationBar.appearance().titleTextAttributes = [NSAttributedStringKey.foregroundColor:UIColor.white]
       ...
    }
}

Calling Appearances.set() in the didFinishLaunchingWithOptions method gives all navigation bars a blue tint, with white buttons, and white titles. And I never have to think about it again. Better yet, say your designer decides to change the button tint color, or title font, etc. With this method, you have just one location to update! Boosh, programming like a boss!

Most view properties (background color, tint color, font, etc) are accessible via the appearance protocol, though I have noticed that the autocomplete is a bit sketchy.

Another cool feature this protocol gives your apps is that you can specify custom appearances for classes when they are contained in specified other classes via the appearance(whenContainedInInstanceOf:) function. For example, say we want all of our navigation bar buttons (UIBarButtonItem‘s) to have a white tint, but if they are in a tool bar, we actually want they to be blue. If this is the case, we can refine our set() function above like so:

static func set(){
    UINavigationBar.appearance().barTintColor = UIColor.tardisBlue
    UINavigationBar.appearance().tintColor = UIColor.white
    UINavigationBar.appearance().titleTextAttributes = [NSAttributedStringKey.foregroundColor:UIColor.white]
        
    UIBarButtonItem.appearance(whenContainedInInstancesOf: [UINavigationBar.self]).tintColor = UIColor.white
    UIBarButtonItem.appearance(whenContainedInInstancesOf: [UIToolbar.self]).tintColor = UIColor.tardisBlue
}

 

TL;DR

Use the appearance method to set app wide custom colors, fonts, etc. Be sure to set these properties before the view is loaded on screen.

import UIKit

struct Appearances{
    static func set(){
       //--- Set your custom appearance here ---
    }
}

Call Appearance.set() in your app delegate’s didFinishLaunchingWithOptions to apply these changes globally.

Sources

https://developer.apple.com/documentation/uikit/uiappearance

http://nshipster.com/uiappearance/