Note: This is full-on, hardcore Computer Science right here. If you’re here to read about my cute doggie or how I’m the best boyfriend ever, this post isn’t for you.
Want the code first?
Head over to the KNKVC page of my company’s site to download it.
I’m an Objective-C/Cocoa programmer by heart, but because of my job I also regularly program on Windows. Now, since I’m a Mac developer you’re probably expecting me to harp on about how Windows programming is soul-destroying and crappy and blah blah blah. Well, no. If you’re doing the right kind of programming, it’s exciting and interesting no matter what platform you code on. In fact, if anything the satisfaction of coding something awesome on Windows has a greater feeling of accomplishment at the end because the bar is set so low in the Windows software field. When do you something truly great it stands head and shoulders above the rest. My favourite kind of programming is framework programming. Designing a component outside of the complexities of a whole application allows you to design beautiful, clean APIs and components. In fact, every single application I write or am a part of writing ends up shipping in components. Music Rescue for Windows, for instance, has a library for talking to iPods which I wrote, a library containing user interface controls which I wrote and the application (which my colleague wrote) imports them both and assembles it all together into an application.
Shameless Copying
When I first started working in .NET, it became very clear very quickly
that it wouldn’t be as easy as the Mac version. The main problem being
the the built-in table control is absolutely terrible, and Music
Rescue is pretty much all tables. Every time I saw something I disliked
in .NET, I’d exclaim “Cocoa does this much better!”. Some of the time
is was down to preference, but others (like the table) the .NET version
simply wouldn’t do. Eventually, I started implementing bits of Cocoa in
.NET. First came the token field, then the table view and its supporting
classes (cells, columns, etc etc). One of the greatest things in the
Cocoa Framework, though, is Key-Value Coding (KVC) and Key-Value
Observing (KVO). Key-Value Coding allows you to get and set values of
objects without actually caring what they are. It provides nifty little
features when combined with arrays and dictionaries. Say you have an
array of chairs. Want a list of the chairs’ colours?
chairArray.valueForKey("colour");
gets you that list. Key-Value
Observing allows you to be notified when a property of a particular
object is changed. For example, if you want to know when the number of
legs your chair has changes (perhaps one breaks off?), add yourself as
an observer to the legs property of the chair and you’ll automatically
be told when that value changes. This is an amazingtool if used
properly, and severely reduces coupling. In fact, Key-Value Observing
has made the model so separate from the rest of the app in Clarus, the
entire model is in a separate assembly and I don’t subclass once in the
application.
How does it work? Magic, right?
Three things made me attempt to implement KVC/O in .NET…
-
For ages I’ve pondered how Key-Value Observing in Cocoa works. I get about 90% of it, but how does it automatically know when the properties are changed? I don’t do anything special - just set an iVar.
-
I’ve become so used to KVC and KVO, I find it hard to write code without them.
-
I should really get round to learning C#. Visual Basic is great, but annoyingly restrictive at times. So, how better to learn a brand new programming language than by implementing something I don’t understand? Great!
The Nitty Gritty
First, KVC. KVC is pretty easy to implement, to be honest, and the
entire KVC system is only around 400 lines of C#. I declare a couple of
extension classes to the Object - one for getting and the other for
setting, just to keep it separate. When you call valueForKey
, it simply
uses System.Reflection
to look for a property for that key and invokes
the getter to retrieve the value. If a property isn’t found, we look for
a method that returns a value with the same name as the key. If that
doesn’t work, we call valueForUndefinedKey
to throw an exception or do
whatever custom logic subclasses might want to do. Setting is more or
less the same story, but if a property can’t be found we look for a
method called set<Key>
that takes a single parameter and call that.
Finally, I implement a few specific extension methods for arrays and
dictionaries to get that juicy feature set I was speaking about.
KVO
KVO is harder and more complex. The line count isn’t much higher - around 440 lines of C# but they’re 440 hardcore lines, unlike the KVC implementation that’s mostly full of null checking. Unfortunately, it’s not simple enough to describe succinctly with words. Allow me to present… a diagram:
At the moment, let’s just pretend the automatic stuff actually works and
KNKVOCore
gets notifications of then a property is about to change and
when it has changed. Using these notifications, it looks through the
list of ObservationInfo
objects and if the key and object match, tells
them that their property will or has changed. In turn, the
ObservationInfo
object, depending on the options set by the observer,
sends out change notifications. Each ObservationInfo
is responsible for
one observation of an object by an observer.
At the moment, this is all very simple. However, it gets complicated
when we start dealing with key paths. A key path is a trail through an
object tree, and look very much like dot notation in Java or C# or
whatever. So, let’s say we want to observe house.kitchen.drawer.utensilCount
.
Easy, right? We just valueForKey
our
way down to drawer
and observe utensilCount
, right? Fine, but what
happens if someone else says kitchen.drawer = new drawer()
?
Suddenly, the drawer instance we’re observing isn’t the same as
house.kitchen.drawer
, and our observation is now either observing the
wrong thing, or observing nothing since memory management deallocated
the object. What we need to do is observe every single step of the key
path for changes, and when items change, remove the observer from the
old value and add it to the new one. All this happens under the hood
since the client doesn’t care about what kitchen the house has, or what
drawer it has - just how many utensils are in the house’s kitchen’s
drawer. Enter KNKVOKeyPathObserver
. When the client wants to observer
a key path, KNKVOCore
creates one of these bad boys instead of an
ObservationInfo
. The KeyPathObserver
then runs through the key path,
observing each part of the path on the relevant object, for which
KNKVOCore
creates an ObservationInfo
as discussed before. When it
receives a change notification from any of its objects, it removes its
observers from the entire tree of old objects and adds new observers to
the new tree, sending change notifications back to the original
observer as needed.
Automatic?
Up until now we’ve been assuming that KNKVOCore
can get notifications
just before and after properties of objects change. This isn’t so easy
in C#.NET. The Cocoa version of KVO allows you to manually specify when
you’re going to change a value:
[self willChangeValueForKey:@"name"];
// Some really complex logic to determine a new name
[self didChangeValueForKey:@"name"];
I’ve implemented this in my version as
well, so currently properties have to do a similar thing to get noticed
by KVOCore
. Which is totally lame, since I can’t modify all the
properties in existing .NET classes. The next solution is to add those
will/didChange… calls into the setValueForKey
methods. This is less
lame because I can call setValueForKey
on anything. However, all the
other classes aren’t going to use those, so I can only observe when I
change they keys, which is dumb since I know that the value changed,
because I just changed it. See?
The next solution is to use .NET’s INotifyPropertyChanging
and
INotifyPropertyChanged
interfaces, but
barely anything actually uses them.
Next!
The next solution is to get clever with Reflection.Emit. Using Reflection.Emit allows you to dynamically override methods, but unfortunately you have to create a dynamic subclass to do so, rendering this approach useless as well - I want auto-KVO on existing instances, not new ones. Next, we find PostSharp. PostSharp is an aspect-based framework, allowing the injection of methods that get called just before and just after the target method. This is perfect! Unfortunately, PostSharp does this either on compile or load time, meaning we can’t do it dynamically at run time. Damnit!
…and that’s it. I’ve now run out of solutions. Microsoft are currently testing .NET 4.0, which promises more dynamic…ness, including dynamic dispatch. Hopefully that’ll allow me to implement automatic KVO, but since I’d like to release the next update to my app before the end of 2010 I might be using manual KVO in my next .NET project (Music Rescue 5.0).
What Now?
Well, I release some code, of course! Although it’s not working as automatically as I’d like, this has been a great technical exercise, and one of the most difficult problems I’ve tackled in programming - especially the implementation details of observing a key path. I’ve learned how KNO isn’t actually magic, and although I doubt my implementation of it is a great as the one in Cocoa, it works well enough to be useful in shipping products. Hell, this’ll be an integral part of Music Rescue 5.0, automatic or not! You can find the KNKVO code and documentation over at the open-source section of my company’s website.