/KVOblocks

A block API for using Key Value Observing with Macruby

Primary LanguageRuby

KVOblocks for MacRuby

Inspired by KVO+Blocks for Objective-C, KVOblocks brings a cleaner API to using Key Value Observing in Macruby.

Example Usage

Key Value Observing can be very verbose and require a significant amount of boilerplate code. The KVOblocks module aims to remove all the excess code and create a succinct, readable API. Here is an example of what Key Value Observing looks like without using the KVOblocks module. We have a ‘Bus’ class that is initialized with a number of passengers. Let’s say these passengers are actually rhinos, they’re going to have quite an impact on fuel consumption, therefore every time the passenger number changes the miles_per_gallon needs to be recalculated:

class Bus
    attr_accessor :passengers

    def initialize(passengers)
        @passengers = passengers
        self.addObserver(self, forKeyPath:'passengers', options:0, context:nil)
    end

    def observeValueForKeyPath(path, ofObject:object, change:change, context:context)
        if object == self && path == 'passengers'
            calculate_miles_per_gallon
        else
            super
        end
    end
end

bus = Bus.new(2)
bus.setPassengers(20) # calculate_miles_per_gallon, will be invoked

As can be seen from the above example, not only is the API more verbose than it needs to be, the code that is executed for the observed object needs to be placed in a different method call ‘observeValueForKeyPath(path, ofObject:object, change:change, context:context)’, which obscures the intent of the code. When several observers are added, this problem is compounded further as all observer code needs to be placed in this one method.

Here is the equivalent code using the KVOblocks module:

require 'KVOblocks'
class Bus
    attr_accessor :passengers

    def initialize(passengers)
        @passengers = passengers
        self.add_observer_for_key_path('passengers') { calculate_miles_per_gallon }
    end
end

bus = Bus.new(2)
bus.setPassengers(20) # calculate_miles_per_gallon, will be invoked

The observer and the code to be executed are defined together, greatly improving clarity. If calculate_miles_per_gallon is a CPU intensive operation we don’t want the main thread to become blocked, as the UI will become unresponsive. This problem can be solved with this tiny amendment to the code:

self.add_observer_for_key_path('passengers', async:true) { calculate_miles_per_gallon }

By including the ‘async:true’ argument, calculate_miles_per_gallon will occur in the background and the UI will remain responsive.