KVC in Swift (Part 1)

2 Jul

Last year Brent Simmons wrote about the lack of Key-Value-Coding (KVC) in Swift.

KVC has been around for quite awhile now. Taking a look at the NSKeyValueCoding protocol documentation shows it has been around since OS X 10.0. Which is honestly further back than I remember it being.

It’s not a large API, but it can be very useful.

As the docs say, the basic methods for accessing an object’s values are setValue:forKey:, which sets the value for the property identified by the specified key, and valueForKey:, which returns the value for the property identified by the specified key.

Unfortunately you don’t get those methods in Swift classes and Structs. However, It should be noted that any class that inherits from NSObject does still get KVC in Swift.

Brent mentioned that it would be pretty cool and “Swift-like” to have a syntax in Swift like the following

x["propertyName"]

And I completely agree. So let’s have a go at it.

–––––––––––––––––––––––––––––––––––––

First we need to define a struct to play with.

struct myValue {
    let someString = "Hello World"
    let someNumber = 42
}

let aValue = myValue()

Now we can of course access the properties using the normal syntax like

aValue.someString

But if we don’t know the property we want at compile time and instead have the property’s name as a string we need to do some more work to get at it’s value…

Lets define our KVC protocol

protocol KVC {    
    func valueForKey(key : String) -> Any?
}

For now we’re just going to be implementing  valueForKey: which will allow us to get the value of any property.

We’ll go ahead and extend our KVC protocol and add our default implementation.

extension KVC {
    
    func valueForKey(key : String) -> Any? {
    
        let mirror = reflect(self)
        
        for index in 0 ..< mirror.count {
            let (childKey, childMirror) = mirror[index]
            if childKey == key {
                return childMirror.value
            }
        }
        return nil
    }
}

OK. Let’s break this down a bit. The valueForKey: prototype says that we’re going to take a string and return an optional of Any? Unfortunately I don’t know of a way to have it return a generic (See my follow-up post for the generic version). Or a way to extend Any to make KVC available to all Swift types. If you have any advice let me know on twitter @leemorgan.

The next line is really where things start to get interesting.

let mirror = reflect(self)

Take a look at the reflect() function and you’ll see that it takes a generic and returns MirrorType.

Let’s take a quick sidebar to discuss reflect() and MirrorTypeMirrorType says it “supplies an API for runtime reflection”. Well, what does that mean? It simply means it provides some functions that allow you to get information about a given type / object (e.g. Struct or Class).

If you’re coming from Objective-C then you’re likely familiar with methods like isKindOfClass: and respondsToSelector: those methods are a form of Introspection (Introspection refers to the capability of objects to divulge details about themselves as objects at runtime). You can think of Reflection as a somewhat more powerful form of Introspection.

The parts of MirrorType that we’re interested in are

/// The instance being reflected.
    var value: Any { get }

/// The count of `value`'s logical children.
    var count: Int { get }
    subscript (i: Int) -> (String, MirrorType) { get }

Those methods will allow us to find out what properties our struct has and their values.

Back to our KVC protocol…In the next line we are looping though each property in our struct

for index in 0 ..< mirror.count {

We then get a tuple of the property’s name (childKey) and it’s reflected value (childMirror) using MirrorType‘s subscript method. We don’t so much care about the reflected value (childMirror), it’s just a way for us to access the property’s underlying value.

let (childKey, childMirror) = mirror[index]

Next we check to see if the property (childKey) is the one we’re looking for. If it is, we return it’s value.

if childKey == key {
    return childMirror.value
}

Finally, if we don’t find a match we just return nil

return nil

Now to use this we just need to make our myValue struct conform to the KVC protocol.

struct myValue : KVC { 
    let someString = "Hello World" 
    let someNumber = 42 
}

And then we can use KVC like so

let aValue = myValue()
aValue.valueForKey("someString")

Of course this isn’t the pretty syntax like we wanted, but we can get that really easy. Just add this to our KVC extension

subscript (key : String) -> Any? {
    return self.valueForKey(key)
}

And now we can use it like this

aValue["someString"]

If you’re interested, I’ve posted a Playground demonstrating all this on GitHub.