Rx with Objective-C using NSInvocation

I want to make a point that NSInvocation is a really powerful tool. If we do not care too much about a compiler type checking every single line of our code we can build a scalable Rx implementation using NSInvocation.

NSInvocation is an Objective-C message rendered as an object that can be passed around and invoked when required. Think of it as an old school closure which isn’t anonymous, rather opaque in nature and doesn’t implicitly captures any data, close to C++ lambda expressions. NSInvocation is just a simple wrapper around a function, function object would be the best term to describe it.

NSInvocation is very verbose. Here’s an example of an NSInvocation that captures the print: method:

NSInvocation *verbose = [NSInvocation invocationWithMethodSignature:
                                        [self methodSignatureForSelector:@selector(print:)]];
[verbose setTarget:self];
[verbose setSelector:@selector(print:)];

Since we are going to use NSInvocation quite a lot, it would be wise if we write a nice category to reduce verbosity a bit.

@implementation NSInvocation (RxConvenience)

+ (instancetype)invocationWithTarget:(id)target
                            selector:(SEL)selector
{
  NSInvocation *invoc = [NSInvocation invocationWithMethodSignature:
                         [target methodSignatureForSelector:selector]];
  [invoc setTarget:target];
  [invoc setSelector:selector];
  return invoc;
}

@end

With this change the same expression as above can now be written as:

NSInvocation *lessVerbose = [NSInvocation invocationWithTarget:self
                                            selector:@selector(print:)];

Rx

At the core of Rx is the idea of producers, consumers and events. A producer generates some events and a consumer listens to those events. Sometimes the data expected by the consumer is not exactly what the producer is emitting, in that case we need an operator that knows how to convert data from a form to another. Other times, a consumer needs to listen to events from more than one consumers, for that again we have an operator that can convert multiple data into a single data. As you can imagine these single purpose operators can be very powerful in building giant sophisticated systems for delivering events from producer to consumer. And this plumbing of many small components together to perform a bigger task is what makes Rx so great!

Observables

So, before we go on writing any code lets imagine how do wish our Rx API to look like from the client perspective. If we have a simple print: method as

- (void)print:(NSNumber *)num
{
  NSLog(@"%@", num);
}

We would like Rx to expose a way to create a producer and a way to consume the events, something like:

- (void)run
{
  RxObservable *stream = [RxObservable observableFromArray:@[@1, @2, @3]];
  NSInvocation *printInvoc = [NSInvocation invocationWithTarget:self
                                                selector:@selector(print:)];
  [stream subscribe:printInvoc];
}

As a initial step let’s define a base class called RxObservable that provides this interface. And since we don’t want to bloat this class with every spec implementation, let’s provide some factory methods to some internal implementation specific class.

@implementation RxObservable

+ (instancetype)observableFromArray:(NSArray *)array;
{
  return [RxArraySource createWithElements:array];
}

- (void)subscribe:(NSInvocation *)invocation;
{
    // Needs to be subclassed
}

@end

And then the actual implementation could look something like

@interface RxArraySource : RxObservable

+ (instancetype)createWithElements:(NSArray *)array;
- (void)subscribe:(NSInvocation *)invocation;

@end
@interface RxArraySource ()
@property (nonatomic, copy) NSArray *elements;
@end

@implementation RxArraySource

+ (instancetype)createWithElements:(NSArray *)elements;
{
  RxArraySource *instance = [RxArraySource new];
  instance.elements = elements;
  return instance;
}

- (void)subscribe:(NSInvocation *)invocation;
{
  for (id element in _elements) {
    [invocation setArgument:&element atIndex:2];
    [invocation invoke];
  }
}

@end

With that in place, our run method should be working as expected.

1
2
3

Operators

Next lets see how can we go about writing an Rx operator. One of the most commonly used Rx operator is the map operator which simply converts data from one type to another. Again, thinking upside down, how would we want our API to look like in the end.

If we have a standalone method like

- (NSNumber *)tenTimesOfNumber:(NSNumber *)num
{
  return [NSNumber numberWithInteger:[num integerValue] * 10];
}

We should be able to create a map operator which can then be plugged in with rest of the pipeline

- (void)run
{
  RxObservable *stream = [RxObservable observableFromArray:@[@1, @2, @3]];
  NSInvocation *printInvoc = [NSInvocation invocationWithTarget:self
                                            selector:@selector(print:)];
  NSInvocation *mapInvoc = [NSInvocation invocationWithTarget:self
                                 selector:@selector(tenTimesOfNumber:)];
  [[stream mapWithInvocation:mapInvoc] subscribe:printInvoc];
}

Lets extend our RxObservable to provide an interface for this, which again would be more like a factory method to an implementation specific class.

@implementation RxObservable

// ...

- (instancetype)mapWithInvocation:(NSInvocation *)invo
{
  return [RxMapOperator operatorFromStream:self withInvocation:invo];
}

@end

Notice since we are always passing RxObservable from our fat base class, we can easily chain RxObservables together. Now let’s go and write RxMapOperator

@interface RxMapOperator : RxObservable

+ (instancetype) operatorFromStream:(RxObservable *)stream
                     withInvocation:(NSInvocation *)invoc;

@end

The thing to realize with a map operator is that it knows not a lot about the actual types. It is provided with an actual implementation from the client that knows how to map types. So RxMapOperator only needs to chain two methods, take returned value from one and pass it down to the another.

@interface RxMapOperator ()
@property (nonatomic, strong) RxObservable *stream;
@property (nonatomic, strong) NSInvocation *invocUp;
@property (nonatomic, strong) NSInvocation *invocDown;
@end

@implementation RxMapOperator

+ (instancetype) operatorFromStream:(RxObservable *)stream
                     withInvocation:(NSInvocation *)invoc;
{
  RxMapOperator *instance = [RxMapOperator new];
  instance.stream = stream;
  instance.invocUp = invoc;
  return instance;
}

- (void)transform:(id)arg
{
  [self.invocUp setArgument:&arg atIndex:2];
  [self.invocUp invoke];

  id ret;
  [self.invocUp getReturnValue:&ret];

  [self.invocDown setArgument:&ret atIndex:2];
  [self.invocDown invoke];
}

- (void)subscribe:(NSInvocation *)invocation
{
  self.invocDown = invocation;
  [self.stream subscribe:[NSInvocation invocationWithTarget:self 
                                        selector:@selector(transform:)]];
}

@end

With that in place our run method should now print

10
20
30

Parting thoughts

This experiment was just to play around the idea of building something quick and dirty with NSInvocation. Obviously none of this code is production ready, but I think it can be expanded to cover all sort of building blocks for Rx patterns.

Here is the source code, feel free to checkout and play with it gist.github.com/chunkyguy/0f67f074c974aac7024d153011e5400e