Computer science no. 1: abstraction is the main mechanism through which complexity can be reduced. *
Key-Value Observing (KVO) is a very useful abstraction in Cocoa that provides a hook allowing you to attach code whenever a particular piece of state changes. This keeps the update code in one place, and avoids the need to remember to keep things in sync. Once you get used to using KVO, you will wonder how you ever lived without it.
Then when you come to develop OpenGL ES for the iPhone, you suddenly find yourself reduced to the stone age - key value observing doesn’t work. This is because OpenGL ES is exposed on the iPhone solely through the CAEAGLLayer class (a subclass of CALayer). Core Animation layers expose their presentation layer state through properties (such as ‘bounds’, ‘position’, ‘transform’) which will update every frame during animation. Each update of each property would trigger a KVO notification, which in turn potentially triggers several other methods in each observer for that property. Considering that each element of the entire UI is constructed out of layers, this can quickly add up (or in fact multiply - there are a lot of ‘each’s there). As a result, Apple disabled KVO - for very valid performance reasons.
But in many situations in your own code, you know in advance that your layer isn’t going to be animated very often. The most common case is where your application’s main view simply presents OpenGL ES content. Such a layer may house the majority of your application’s code base, and KVO would be a godsend.
Do not fear - you can enable it again. Here’s how.
The first hint is the class hierarchy: YourGLLayer > CAEAGLLayer > CALayer > NSObject. Key-value observing is implemented in NSObject (via the NSKeyValueObservation category), and other classes that inherit from NSObject on the iPhone OS support KVO. CALayer expressly disables KVO by overriding the NSKeyValueObservation category methods. We restore KVO again by overriding these methods in our own layer class, making them call NSObject’s original implementation of the corresponding method.
Here’s all the code you need:
#include <objc/runtime.h>
@implementation YourGLLayer
#pragma mark Key Value Observation Support
// Note: In each case, CALayer's implementations are bypassed
// entirely, otherwise multiple updates can be reported.
- (void)addObserver: (NSObject*)observer
forKeyPath: (NSString*)keyPath
options: (NSKeyValueObservingOptions)options
context: (void*)context
{
// Call NSObject's version of the method.
IMP imp = class_getMethodImplementation([NSObject class],
_cmd);
imp(self, _cmd, observer, keyPath, options, context);
}
- (void)willChangeValueForKey: (NSString*)key
{
// Call NSObject's version of the method.
IMP imp = class_getMethodImplementation([NSObject class],
_cmd);
imp(self, _cmd, key);
}
- (void)didChangeValueForKey: (NSString*)key
{
// Call NSObject's version of the method.
IMP imp = class_getMethodImplementation([NSObject class],
_cmd);
imp(self, _cmd, key);
}
// CALayer disables automatic key value observation
// notifications. Restore NSObject's default behaviour.
+ (BOOL)automaticallyNotifiesObserversForKey: (NSString*)key
{
return [NSObject automaticallyNotifiesObserversForKey:
key];
}
... // Your layer code.
@end
This restores ordinary KVO behaviour just for your layer, which gives you the best of both worlds.
Naturally you can apply some abstraction here of your own - wrap up the method overrides above in a new class CAEAGLLayerWithKVO (a subclass of CAEAGLLayer), and make your layer class derive from CAEAGLLayerWithKVO instead. That keeps your layer code as simple as possible.
A future post will look at ways of making KVO easier to use, and even more powerful.
* I’m guessing - I didn’t do a computer science degree.