Property Lists And User Defaults in Swift

I have written a bit about some of the work I am doing right now. Sadly, for most of this calendar year I have worked with Swift very little. I spent a lot of time working with some old open-source C libraries. Trying to yank my brain from the C precipice and trying to force it back into thinking in Swift.

My boss Brad Larson has been diving into the deep end of the Swift pool and bringing a bunch of treasures up from the bottom that I never would have considered. I am taking one of these treasures and examining how it works and what issues in our code it solves. I am hoping that by taking an elegant solution that many of us might not have thought of that we can all start thinking outside the OOP box.

The first part of this blog post is talking about some Cocoa concepts you probably are familiar with if you are not a new developer. Since I am a new developer, this stuff was new to me and interesting, so if you’ve been programming a while, you can probably skim this first section.

NSCoding, Property Lists, and Objective-C

The main piece of software we utilize at my company is SonoGuide. This is a piece of software that controls our robotics systems. Our systems print very minute amounts of liquid on a very small scale. Our positioner systems must be very precise. Our systems are rated to be accurate down to five microns. For those who haven’t worked with measurements for a while, that is 0.005 millimeters. You know, those super tiny increments on the ruler that are smaller than the tip of your pencil. So we are dealing with things that are incredibly small.

One of the pieces of functionality that we want our users to have is to be able to save coordinates. Since our systems are so precise and measure such small distances, it would be rather annoying for them to have to remember where their solution wells are in relation to the substrate they are printing on. We would like these positions to persist even if the user turns off the computer for the day and comes back next week.

It makes the most sense to keep these locations in the NSUserDefaults, which is a Property List. Property Lists, or plists, are NSDictionaries that contain different types of data. They can include arrays, strings, numbers, and even other NSDictionaries.

So we need an array of coordinate objects that we want to store in the NSUserDefaults that we are able to store and retrieve when necessary.

The way we, and everyone else, accomplished this back in the before time was as follows:

Serialization and NSCoder

While we’re still in the Way Back machine, do you remember that there was a period of time where if you looked at crash logs, you got weird hexadecimal values for where your app crashed rather than a nice, neat, human readable line of code?

The process of turning that value into a line of code is called “serialization.” The best mental explanation I have for serialization is the explanation that Mike Teevee uses to explain what television is in Willy Wonka and the Chocolate Factory:

You photograph something, and then the photograph is split up into millions of tiny pieces, and they go whizzing through the air down to your TV set where they’re all put together again in the right order.

Except instead of a photograph, you are using data and instead of it whizzing through the air, it’s whizzing through your hard drive in an area where it can be persisted and reproduced accurately.

We used the archiving functionality built into Cocoa to do our data serialization. Our persistence needs were not large enough to justify using Core Data and I think it’s possible that Core Data didn’t exist when the project was created. For a good background on choosing how to persist data, read Mattt Thompson’s post on the topic.

Archiving functionality has a few pieces. It has NSKeyedArchiver and NSKeyedUnarchiver for pushing and pulling the data. It also has NSCoder, which is where we, as the programmer, specify how to encode and decode our data.

NSCoding is a protocol that requires two methods to be written to conform to the protocol: encodeWithCoder and initWithCoder.

We implement the initialization protocol in our Coordinate class:

#pragma mark -
#pragma mark NSCoding protocol methods

- (void)encodeWithCoder:(NSCoder *)coder;
{
    if (name != nil) {
        [coder encodeObject:name forKey:@"name"];
        [coder encodeInteger:x forKey:@"x"];
        [coder encodeInteger:y forKey:@"y"];
        [coder encodeInteger:z forKey:@"z"];
    }
}

- (id)initWithCoder:(NSCoder *)coder;
{
    self = [self initWithX:[coder decodeIntForKey:@"x"] 
                      andY:[coder decodeIntForKey:@"y"] 
                      andZ:[coder decodeIntForKey:@"z"]];
    if (self != nil)
    {
        self.name = [coder decodeObjectForKey:@"name"];
    }

    return self;
}

With this code we are simply encoding the name of our coordinate object as a key and then adding the x, y, and z locations as values.

This takes care of our issues with serializing our coordinates. The coordinates are converted into NSData, making it nice and easy for us to move it from one place to another.

So cool, we have encoded our coordinate and we can extract it by decoding it. Huzzah.

So how do we decode our coordinate?

Our coordinate is stored as NSData. We have to reach out to our NSUserDefaults, look for anything encoded with the key “savedPositions”, decode it, and bring it back here.

NSData *dataRepresentingSavedPositions = [[NSUserDefaults standardUserDefaults]
                                           objectForKey:@"savedPositions"];
if (dataRepresentingSavedPositions != nil)
{
    NSArray *oldSavedPositions = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedPositions];
}

If you are interested in learning more about the NSCoding process, read this amazing blog post by Mike Ash.

Problems With This Approach

If you have learned anything about archiving data, this is the method you were probably taught. I did this myself while I was working through the Big Nerd Ranch guide to iOS programming. This is the standard. What is wrong with it.

Quite a bit, actually.

NSData isn’t inherently safe.

NSData is basically grey goo. It is a large clump of bits and bytes that have things fed into it and pulled out of it.

In our dataRepresentingSavedPositions we are simply pulling out any values associated with the key savedPositions. We are trusting that the data we are pulling is the kind that we want. It’s possible that our users have gone into the defaults property list and changed something. It’s possible that a hacker has gone and replaced one of our coordinates with a malicious executable file. We don’t know. There are no safety checks to ensure that none of these situations occur because when we serialized our data, we lost its type information. We’re living on a prayer here.

NSCoder can only be applied to an object inheriting from NSObject.

The way we structured our Swift code, we take advantage of the enhanced features of structs and enums. Our structs cannot comply to the NSCoder protocol because the only things that can are objects that inherit from NSObject.

In Mike’s article about implementing NSCoding, he mentions that if you want to encode a struct, you can’t encode it directly because it breaks. You need to break a struct down into its base components if you want to store and serialize them.

Since Apple has made it rather clear at this most recent WWDC that they would like us to start moving away from making everything an object, it doesn’t make a lot of sense for us to completely rewrite our code to start making our coordinates objects rather than structs.

Basically, NSCoder is build around the idea that you will only work with and encode objects, which is not how many of us program anymore.

A Swift Approach

Since we can’t really do our serialization and archiving the way we did them in the previous version of the code, how did we approach and solve this issue?

I will go over the various components of this code.

Structs

Right off the bat, we are creating our coordinate as a struct and not an object:

struct Coordinate {
    let x:Double
    let y:Double
    let z:Double

    init(_ x: Double, _ y: Double, _ z: Double) {
        self.x = x
        self.y = y
        self.z = z
    }
}

Since we know from earlier that we can’t associate NSCoder with our Coordinate struct, we need to figure out another way to deal with it. We used a protocol.

Protocols

protocol PropertyListReadable {
    func propertyListRepresentation() -> NSDictionary
    init?(propertyListRepresentation:NSDictionary?)
}

Since property lists are just NSDictionary objects, we need to make sure that anything that conforms to PropertyListReadable is able to create and take in NSDictionary objects.

Since we can now extend structs with protocols, we are able to take the PropertyListReadable protocol and extend the Coordinate struct:

extension Coordinate: PropertyListReadable {
    func propertyListRepresentation() -> NSDictionary {
        let representation:[String:AnyObject] = ["x":self.x, "y":self.y, "z":self.z]
        return representation
    }

    init?(propertyListRepresentation:NSDictionary?) {
        guard let values = propertyListRepresentation else {return nil}
        if let xCoordinate = values["x"] as? Double,
               yCoordinate = values["y"] as? Double,
               zCoordinate = values["z"] as? Double {
                   self.x = xCoordinate
                   self.y = yCoordinate
                   self.z = zCoordinate
        } else {
            return nil
        }
    }
}

The propertyListRepresentation function works under the assumption that a Coordinate instance was already able to be initialized with the correct number and types of parameters. This function goes through the Coordinate instance and sets key-value pairs for each of the axis and returns an NSDictionary. This function allows you to take any and all Coordinate instances and format them in such a way that they can be saved to the NSUserDefaults.

There are some situations where you want to go the other way. You have extracted an NSDictionary from the NSUserDefaults and you want to break it back down into a Coordinate instance. Since you can’t be certain that the data you are pulling from the defaults is in fact a completed coordinate, you need to use a failable initializer. You also need to make sure your return value is optional because if you were pulling data associated with a string you don’t want to crash your app by trying to process nonsense.

In our first line of code, we are implementing the shiny new guard statement. Since many of you may not have had time to dabble with guard, let me walk you through how we are using it and why.

Our goal with this initializer is to create a Coordinate with values we have extracted from the user defaults. Before we try to set each of our axis to a value, we want to make sure a value even exists. That is where guard comes in.

We are checking to see if our optional parameter was passed in or if we have nil. If we have nil, we don’t want to have to go through a bunch of conditional logic to verify that yes, we are doing nothing with our value. A guard statement only executes a line of code if the condition is not met. We want to return nil if we know that no parameters were passed in, so we can cut to the chase and do that immediately at the beginning of our block of code.

Once we have established that our NSDictionary was not nil, we now go through each value in the dictionary to make sure it correlates to a specific key. If at any point we encounter a key that does not have a value, we kick out of the whole thing and return nil.

By using a protocol to implement this functionality, we are able to specialize and reuse these concepts for other value types in our software. Before, we would have had to make a superclass or a delegate with methods that were required to be overridden by the subclass. By allowing structs to conform to protocols, we are able to specify functionality that we need in multiple places but with different implementation.

Map, Filter, and FlatMap

It would not be a shiny Swift blog post without the obligatory section on the functional features of Swift.

Just to clarify where we are at at this point in the code. NSUserDefaults is an NSDictionary where the key is the name you assign to label what you want to store. In our case, we want to store saved coordinates, so our key is “savedPositions”. The value is an array. The array can contain any object. Our Coordinate is not an object because it’s a struct, which is why we convert it to an NSDictionary. So, in theory, our NSUserDefaults should have a key-value pair where the key is “savedPositions” and the value is an array of NSDictionary objects that represent Coordinates. Simple, right?

Wouldn’t it be really cool to be able to filter through the array we pull from the NSUserDefaults to make sure we have an array of only the objects of the type we want? Sadly, there isn’t.

Of course there is. If there wasn’t then either that sentence wouldn’t be there, and or I would be a sadist. The jury is still out on that last conditional.

func extractValuesFromPropertyListArray(propertyListArray:[AnyObject]?) -> [T] {
    guard let encodedArray = propertyListArray as? [NSDictionary] else {return []}
    return encodedArray.map{T(propertyListRepresentation:$0)}
                       .filter{ $0 != nil }
                       .map{ $0! }
}

In the extractValuesFromPropertyListArray function, we are accepting an optional argument of an array of any object and returning an array of our generic return value. We also are specifying our generic return value must conform to the PropertyListReadable protocol.

The first thing we need to do is make sure we actually received a value. If we didn’t, there is no point in filtering an array that doesn’t exist. Our first line of code checks to see if we have a value and if that value can be cast to an array of NSDictionaries. If we don’t or we can’t, we return an empty array.

If that passes, we go ahead and filter the array. We use the .map function (which iterates through an array, applies a function to it, then creates a new array from the results) to run each value through our propertyListRepresentation initializer we created in our Coordinate extension. The propertyListRepresentation either returns an optional Coordinate or a nil.

We then apply a .filter to our results. We check to see if each item in the array is not nil. If it is nil, we throw it away. We then create a new array of just optional Coordinates.

Finally, now that we have ensured that we don’t have any possible nil values, we use another .map to unwrap each value and return and array of Coordinates (or any other structs we extend with the PropertyListProtocol).

This is really inefficient.

The function extractValuesFromPropertyListArray was refactored to the following:

func extractValuesFromPropertyListArray(propertyListArray:[AnyObject]?) -> [T] {
    guard let encodedArray = propertyListArray else {return []}
    return encodedArray.map{$0 as? NSDictionary}
                       .flatMap{T(propertyListRepresentation:$0)}
}

It was a real shame in our previous version of the code that if we had an array of mostly NSDictionaries, but if some stupid NSData object (which could be a malicious program) somehow snuck into our array, we had to throw away all of our other valid values. Since we’re going to filter through our results later anyway, we just want to verify we have an array of anything.

Since we don’t filter for NSDictionaries in our first line of code, we need to move the filter to our second line. Now our first .map statement is checking to see if each item can be cast the an optional NSDictionary. If it can, it gets added to the new array. If it can’t, it gets kicked to the curb.

Our previous code had a convoluted setup to be able to apply a function, filter out the nils, then unwrap the coordinates. This is a common enough thing that there should be a more concise way to do this. There is, and that is using .flatMap. Flat Map does exactly the same thing as our previous three chained functions in one handy dandy function call. Huzzah!

Saving to Defaults

We have one last part left to our property list loading and unloading. We’ve verified that everything we are extracting to the defaults is safe. We now have to verify that everything we are sending out to the defaults is safe.

One last little block of code:

func saveValuesToDefaults(newValues:[T], key:String) {
    let encodedValues = newValues.map{$0.propertyListRepresentation()}
    NSUserDefaults.standardUserDefaults().setObject(encodedValues, forKey:key)
}

For our parameters, we are taking an array of values that conform to T:PropertyListReadable and a string value to use for our key.

We’re creating an array of encoded values by taking the array parameter and mapping each one using the propertyListRepresentation function to turn it into an array of NSDictionaries. We then run the result through the setObject:forKey function associated with the NSUserDefaults.

Conclusion

I created a playground that contains all of the code I spoke about in the second half of this post.

When Brad showed me the extractValuesFromPropertyListArray function, it made my head explode. I saw the .flatMap() function and the guard statement, I felt stupid and inadequate. I felt despair that I would ever be able to come up with this stuff on my own. I can look at code and kind of grok what it means, but reading and understanding something are different than actually coming up with it.

After a while of struggling with the code, I realized I was asking the wrong questions about it. Rather than asking “how” it worked, I should have been asking “why” it was written that way. I talked to Brad about how we were dealing with the user defaults in the original code. He told me about the issue he had with it that I detailed in the first half of the post.

That is when a lot of this stuff began to click into place for me.

We don’t have this complicated code because we’re trying to be clever and use all the new toys in Swift. We are doing the code this way because it solves a problem that we had that could not be solved with Objective-C.

We could have used a “for-in” loop instead of the .map() function. We didn’t have to use .flatMap(), we had a longer implementation of it.

I have been approaching learning Swift all wrong. I was trying to take concepts like currying and trying to find ways of cramming them into my code so that I could understand how they worked. Our code is elegant because it had a specific set of problems it needed to solve that either were not solvable any other way, or would have resulted in more lines of code that would have taken more time to run because it wasn’t optimized.

For me, I took two lessons from this:

  1. Instead of spending more time thinking about clever ways to use shiny new features, I should spend some time thinking about what problems I had to solve.
  2. Just because my code “works” doesn’t mean it can’t be better. There might be “easy” ways to get something running, but there might be better, more elegant ways to write your code.

Yes, it’s overwhelming keeping up with all the new language changes and feature out there, but there can be great features that can make your code better that you never discover if you just decide your code is done once it compiles.