01 March 2017

Your app’s data model defines the value that you are going to deliver to the world through your creation. The kinds of information we can consume or create with your app are enabled by this fundamental software component. It tells us whether your app can serve a gourmet palate or if it is only there to be a snack.

While being one of your app’s most important parts, it can be boring to implement. The code typically only involves transforming data from one type to another in a repetitive manner and this can end up being tedious to implement. However, I want to show you that making model objects can still be interesting and exciting and not just a routine task.

Here’s What I Cover

  • I discuss model objects in terms of integrating Swift with Firebase. The model objects in Swift will correspond to a JSON data model implemented in Firebase. I will use actual burial data from my NMCP Finder app for my example.
  • I will show the long, tedious form of creating model objects using if-let unwrapping on each property.
  • I will factor out the use of if-let for unwrapping.
  • I will apply operator overloading to further reduce repetitive code when creating model objects.

I am creating model objects through my own Swift code. I use a third-party library, SwiftyJSON, to ease the retrieval of JSON data. There are other libraries that can go a step further and map your model objects to JSON. Some of these include Argo, ObjectMapper and Unbox. Their drawback is the loss of fine control and understanding you get when writing your own code along with having another dependency to maintain. With Swift 3, creating model objects can be efficient enough where there is not much to gain from employing a third-party library for this task.

JSON Schema for a Burial Model Object

Let’s have a look at the JSON that I will be using for my example. It is in a form that shows the keys and their value types. This is one way to establish a data model for Firebase.

 1 {
 2     burialID : {
 3       "birth" : string,
 4       "branches" : [ string ],
 5       "buriedAt" : {
 6         "marker_id" : string,
 7         "section" : string
 8       },
 9       "death" : string,
10       "firstName" : string,
11       "lastName" : string,
12       "middleNameAndOrOtherName" : string,
13       "ranks" : [ string ],
14       "relatedAs" : [ string ]
15     }
16 }

Making a Swift Model Object

It’s pretty straightforward to translate this JSON dictionary into a Swift struct. Here are the properties that correspond to the keys in the JSON schema.

 1 /// A burial record.
 2 struct Burial
 3 {
 4     var birth                    = ""
 5     var branches                 = [Branch]()
 6     var buriedAt                 = BurialSite()
 7     var death                    = ""
 8     var firstName                = ""
 9     var lastName                 = ""
10     var middleNameAndOrOtherName = ""
11     var ranks                    = [Rank]()
12     var relations                = [RelatedAs]()
13 }

The task is now to load the data from the JSON retrieved from Firebase into this native Swift data structure. Let’s first do it the long way using if-let unwrapping to get all the values from the JSON into our struct.

 1 /// Init a Burial from raw JSON data retrieved from Firebase.
 2 init(jsonDictionary: [String: AnyObject])
 3 {
 4     let json = JSON(jsonDictionary)
 5 
 6     if let uwBirth = json["birth"].string {
 7         self.birth = uwBirth
 8     }
 9 
10     if let uwBranches = json["branches"].arrayObject {
11         for item in uwBranches {
12             if let uwItem = item as? Branch {
13                 self.branches.append(uwItem)
14             }
15         }
16     }
17 
18     if let uwBuriedAt = json["branches"].dictionaryObject as? [String: AnyObject] {
19         self.buriedAt = BurialSite(jsonDictionary: uwBuriedAt)
20     }
21 
22     if let uwDeath = json["death"].string {
23         self.death = uwDeath
24     }
25 
26     if let uwFirstName = json["firstName"].string {
27         self.firstName = uwFirstName
28     }
29 
30     if let uwLastName = json["lastName"].string {
31         self.lastName = uwLastName
32     }
33 
34     if let uwOtherName = json["firstName"].string {
35         self.middleNameAndOrOtherName = uwOtherName
36     }
37 
38     if let uwRanks = json["ranks"].arrayObject {
39         for item in uwRanks {
40             if let uwItem = item as? Rank {
41                 self.ranks.append(uwItem)
42             }
43         }
44     }
45 
46     if let uwRelatedAs = json["relatedAs"].arrayObject {
47         for item in uwRelatedAs {
48             if let uwItem = item as? RelatedAs {
49                 self.relations.append(uwItem)
50             }
51         }
52     }
53 }

Wow, the if-lets exploded all over the place for a relatively small number of fields. The resulting tedium of having to account for all of them is going to impact our productivity. If-let unwrapping causes so much fatigue because we have to create all those nonsensical temporary constants to hold the value that we want to use.

Eliminating If-Let

Ideal code blocks are short and sweet, clear and concise about what they do.

Can we factor out the use of if-let? Yes, there are other ways to unwrap optionals. The Swift nil-coalescing infix operator consisting of double question marks (??) returns the unwrapped value of an optional if it is not nil. If it is nil, a default value is returned instead.

By assigning all the optional values to temporary constants, we can use this operator to quickly unwrap all of the values. Furthermore, the complex values of arrays and dictionaries can be passed to helper functions as needed.

The following code uses the nil-coalescing operator to unwrap the optional values and is a significant reduction of the if-let mess previously shown.

 1 /// Init a Burial from raw JSON data retrieved from Firebase.
 2 init(jsonDictionary: [String: AnyObject])
 3 {
 4     let json      = JSON(jsonDictionary)
 5     let birth     = json["birth"].string
 6     let branches  = json["branches"].arrayObject
 7     let buriedAt  = json["buriedAt"].dictionaryObject
 8     let death     = json["death"].string
 9     let firstName = json["firstName"].string
10     let lastName  = json["lastName"].string
11     let otherName = json["middleNameAndOrOtherName"].string
12     let ranks     = json["ranks"].arrayObject
13     let relatedAs = json["relatedAs"].arrayObject
14     self.birth = birth ?? ""
15     self.branches = branches.map { self.makeBranches($0) } ?? [Branch]()
16     self.buriedAt = self.makeBurialSite(buriedAt)
17     self.death = death ?? ""
18     self.firstName = firstName ?? ""
19     self.lastName = lastName ?? ""
20     self.middleNameAndOrOtherName = otherName ?? ""
21     self.ranks = ranks.map { self.makeRanks($0) } ?? [Rank]()
22     self.relations = relatedAs.map { self.makeBurialRelations($0) } ?? [RelatedAs]()
23 }

It’s still not quite ideal, is it? There’s still some extraneous creation of constants going on.

Operator Overloading

The temporary constants can be eliminated through the use of an infix operator that assigns the optional value from the JSON directly into the model object. For example, for our birth key in our Burial object, the function syntax will be:

self.birth <= json["birth"].string

The code to achieve that operator overloading uses Swift generics to represent the types. The left argument is defined as inout so that the property that is passed in can be mutated.

1 infix operator <=
2 
3 /// Unwrap the right into the left or return the original value of the left.
4 func <=<T>(left: inout T,
5            right: T?)
6 {
7     left = right ?? left
8 }

Putting that operator into practice is going to alleviate the need to set up any temporary constants and let’s us assign values to our properties directly from the optionals contained in the JSON data.

The final result of applying the techniques of the nil-coalescing operator and our own unwrapping assignment operator is a dramatic lessening of the code needed to unwrap all of the optional values from the burial data retrieved from Firebase.

 1 /// Init a Burial from raw JSON data retrieved from Firebase.
 2 init(jsonDictionary: [String: AnyObject])
 3 {
 4     let json = JSON(jsonDictionary)
 5     birth <= json["birth"].string
 6     firstName <= json["firstName"].string
 7     branches = json["branches"].arrayObject.map
 8                { self.makeBranches($0) } ?? [Branch]()
 9     buriedAt = json["buriedAt"].dictionaryObject.map
10                { self.makeBurialSite($0) } ?? BurialSite()
11     death <= json["death"].string
12     lastName <= json["lastName"].string
13     middleNameAndOrOtherName <= json["middleNameAndOrOtherName"].string
14     ranks = json["ranks"].arrayObject.map
15             { self.makeRanks($0) } ?? [Rank]()
16     relations = json["relatedAs"].arrayObject.map
17                 { self.makeBurialRelations($0) } ?? [RelatedAs]()
18 }

Each assignment could easily fit on one line but I’ve chosen to spread out the closures to a second line for additional clarity.

Helper Functions

Finally, here is the code for the helper functions that I use to create the complex data structures of Swift arrays and dictionaries used in my model object. They also use the nil-coalescing operator to unwrap the array members that come back from Firebase. Arrays are processed using the native map in Swift. The burial site dictionary data is used to create a BurialSite model object that is implemented in a similar way to a Burial.

 1 extension Burial
 2 {
 3     /// Handles buriedAt is nil and handles failed cast to [String: AnyObject].
 4     func makeBurialSite(_ buriedAt: Any?) -> BurialSite
 5     {
 6         let jsonDict = buriedAt as? [String: AnyObject]
 7         return (jsonDict != nil) ? BurialSite(jsonDictionary: jsonDict!) : BurialSite();
 8     }
 9 
10     mutating func makeRanks(_ rankArray: [Any]) -> [Rank]
11     {
12         return rankArray.map { $0 as? Rank ?? "" };
13     }
14 
15     mutating func makeBranches(_ branchesArray: [Any]) -> [Branch]
16     {
17         return branchesArray.map { $0 as? Branch ?? "" };
18     }
19 
20     mutating func makeBurialRelations(_ relationsArray: [Any]) -> [RelatedAs]
21     {
22         return relationsArray.map { $0 as? RelatedAs ?? "" };
23     }
24 }

Conclusion

In this exercise of eliminating if-let unwrapping to create a Swift model object from JSON data retrieved from Firebase, I’ve shown how straightforward and compact it can be to write our own mapping code. Our init of init(jsonDictionary: [String: AnyObject]) was transformed from 53 lines down to a potential 14 by using some programming techniques found in Swift 3.

The overall goal here was not to have the code take up the smallest amount of space. Rather, it was to reduce the nonsense of having to make N number of temporary constants for the N data keys we want when using if-let unwrapping. Employing the nil-coalescing operator (??) and operator overloading along with compact functional mapping allows us to accomplish a more concise result while separating the operations for creating a Swift model object into clear sections.

There are many libraries that can create your model objects for you. I do not recommend using them until you have become proficient with creating your own model objects in standard Swift. You may discover, like me, that libraries are more of a hindrance than a help when it comes to implementing your data model. I started to make my model objects using Argo and came across some obscure errors involving the limits of function currying and decided the effort to debug them wasn’t worth it.

With my method for creating model objects in Swift, I have total control over how every data item is used and all without a single if-let being used for unwrapping of that data. You would only know how beautiful that is if you have also reached the level of fatigue that I have from if-let unwrapping. Additionally, the reduction of third-party dependencies is refreshing. I have thereby shown how invigorating it can be to create your own model objects.



blog comments powered by Disqus