Type Safety in Swift: A Route Back


Following yesterday's post on Type Safety in Swift, I'd like to think about how we can meet the demands of programmers wishing to build a type to store (and amend) data that has specific keys and values, and which is received in a dictionary of mixed types; while at the same time embracing type safety and providing a means by which to enable the return of data in exactly the same format as it was received (with changes maintained). A tall order? Not really.

Starting with a wrapper

Here's a resurrection of the type used in yesterday's post, but with the type name changed from TwinTypes to PersonData:
struct PersonData {
    private var stringDict = Dictionary<String,String>()
    private var numDict = Dictionary<String,NSNumber>()
    var dictionary:Dictionary<String,NSObject> {
        var dictionary = Dictionary<String,NSObject>()
        
        for (k,v) in stringDict {
            dictionary[k] = v as NSObject
        }
        for (k,v) in numDict {
            dictionary[k] = v as NSObject
        }
        return dictionary
    }
    // retrieve keys from dictionaries (all or based on Type)
    var keys:[String] {
        var keys = [String]()
        keys.extend(stringDict.keys)
        keys.extend(numDict.keys)
        return keys
    }
    var keysWithStringValues:[String] {
        var keys = [String]()
        keys.extend(stringDict.keys)
        return keys
    }
    var keysWithNumberValues:[String] {
        var keys = [String]()
        keys.extend(numDict.keys)
        return keys
    }
    
    
    init (dict:Dictionary<String,AnyObject>) {
        for (k,v) in dict {
            if let v = v as? String {
                stringDict[k] = v
            }
            else if let v = v as? NSNumber {
                numDict[k] = v
            }
        }
    }
    subscript (key:String) -> String? {
        get {
            return stringDict[key]
        }
        set(newValue) {
            if numDict[key] == nil && stringDict[key] != nil {
                stringDict[key] = newValue
            }
        }
    }
    subscript (key:String) -> NSNumber? {
        get {
            return numDict[key]
        }
        set(newValue) {
            if stringDict[key] == nil && numDict[key] != nil {
                numDict[key] = newValue
            }
        }
    }
    
}
Note: I've taken the liberty of adding some computed properties to extract keys as well, but aside from that it's the same code as yesterday.

Building a more solid type

With the wrapper in place we can utilise it to construct a Person instance. (An instance that only takes part of the data contained in the PersonData instance and does not concern itself with the rest of the dictionary.)
struct Person {
    var id:NSNumber
    var name:String
    var telephone:NSNumber
    private var pData:PersonData
    
    init(personData:PersonData) {
        id = personData["id"] ?? 0
        name = personData["name"] ?? ""
        telephone = personData["telephone"] ?? 0
        pData = personData
    }

    mutating func personData() -> PersonData {
        pData["id"] = id
        pData["name"] = name
        pData["telephone"] = telephone
        return pData
    }

}

let personData = PersonData(dict: ["id":5674,"name":"Gilbert Smith","telephone":07897645893,"age":56])

var person = Person(personData: personData)
person.name = "Gilbert Jones"
person.personData().dictionary // dictionary reconstituted in exactly the same form as originally received but with amendments
What does it save us to create the PersonData wrapper first and then to create the Person type? The answer to this is two-fold: first, there is a reduction in the casting that needs to take place in the instantiation of the Person instance because the PersonData instance has done this using loops (meaning less code), second we have an easy route back to recreate the structure of the original data.

In summary

PersonData type requires that we know the basic structure of data received but not the exact keys, and Person type assumes knowledge of the exact keys, or at least the keys for which it hopes there are values. By working together they provide a pattern of safety and flexibility, and from here it is possible to imagine having various types for accessing different chunks of a PersonData instance for display across multiple view controllers and for each of these to have the ability to update a restricted amount of the PersonData instance (without corrupting the initial data through changes of form).

At the Swift Summit in London, I'll be presenting these ideas in a more visual way (as part of a broader talk on JSON and Swift) and using code similar to that found here to underpin those ideas.


Endorse on Coderwall

Comments