Complexity Culler
CAEAGLLayer with Key-Value Observing (KVO)

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.

Complexity

This blog is intended to be a repository of solutions and concepts to simplify common application development tasks. As an independent iPhone and Mac developer, I will naturally mostly target Objective-C.

This first post will describe the background - where I’m coming from, and what I’m trying to do.

Complexity

Clearly our goal should be to create great software that a user can just pick up and use. Where ‘pick up’ = ‘buy this version’ and ‘use’ = ‘likely to buy the next version’. In order to achieve that goal, we must refine our ideas as to how the software is used.

  1. Prototype. Keep it simple: restrict the initial scope of the problem - sacrifice functionality. This automatically keeps down the complexity.

  2. Feature growth. The functionality is fleshed out to something that is useful. Complexity explodes.

  3. Clean-up. Now that the idea is better understood, simplify it to its bare essentials.

Complexity comes in two fronts:

  • Complexity to develop and maintain the application.

  • Complexity to use the application.

Both of these must be considered together - they are inseparable. One starts with the idea of what the application is to do, and a rough idea of how the user would achieve that. This is stage 1. There is only one thing that can be known for certain at this stage: the initial idea will be wrong. So, we must implement this initial idea, to see what it is like - to understand how exactly it is wrong. We identify the problems, and come up with solutions to them. These solutions come under stage 2 - we are adding complexity. We then implement these solutions, and evaluate the idea again. This allows us to discover which of these solutions are wrong or incomplete, and so we come up with other solutions to fix or patch them. By the time we are happy that the application loosely achieves the original concept, both the user interface and the code base will have exploded with complexity.

This is where one might be tempted to stop (and is where most commercial and home brew application development work stops). However, if you do that, be fully aware of the consequences.

Since you came up with the app, you know how to use it. Great - that makes one! The more complex it is to use, the more time it will take to explain how to use it, and the less likely a user will be able to pick it up by themselves. Easy - you write documentation, right? The more documentation a user needs to read in order to use your app, the less likely they are to buy it, or to continue to use it. And the more time it will take you to write adequate documentation, and the less time you’ll have for doing the stuff you enjoy.

Since you wrote the code, you know how it works - it’s already loaded into your head. But your memory is leaky - in a few months time, you will not remember how all of your app works. The more complex it is, the longer it will take you to load it back into your head. Furthermore, the more complex it is, the more inertia your code has - the more time it takes to extend it or to change it.

In order to achieve the goal of powerful but simple to use software, we must refine our ideas as to how the software is used. The easiest way to do that is to come up with lots of solutions to the same problem, try them out, then choose the best one. The more solutions we try, the more we understand the problem, and the better our solutions will become. And the corollary: The more complex the code base is, the longer it takes to try out each solution. The less solutions we try, the less well we’ll understand the problem, and the less likely we’ll be able to come up with a simple UI.

The overall message here is that complexity is not always bad - it is a natural part of the evolution of an idea. We aren’t creating toys. However, it also represents the limit of an idea - we each have a finite ability to tolerate complexity. Reducing unnecessary complexity allows an idea to be taken further. This applies to our users too - the less complex the tools we give them, the more of their capacity they can dedicate to exploring their idea. This is the main message of this blog, and the meaning behind its name *.


* Due to this blog being written for fun, articles will not have gone through many editing passes, so the wording will at times be overly complex. The irony is not lost.