Srdan Rasic A software engineer

Implementing Reactive Delegates in Swift Powered by Objective-C

I love statically typed languages. I was always more C++ than Objective-C guy. Knowing that I have compiler watching my back all the time makes me feel better about the code I write. The joy I felt back in 2014 watching Swift introductory streams at WWDC will probably stay with me for a long time.

However, I’ve been facing Objective-C almost every day for more than three years now, and I’ve come to realise the power of dynamic aspects of the language. There are things you can do with Objective-C runtime today that are just not possible with pure Swift.1 Think of KVO, method swizzling, UIAppearance, etc. I hope Swift will eventually also get there as I don’t think it necessarily makes Swift less swifty.

One of those dynamic aspects is Message Forwarding. It allows you to do very cool stuff. The story follows.

The Pattern

Being focused on reactive paradigm I found myself fighting with the delegates. Delegation is one of few fundamental patterns in Cocoa development, but it has no place in reactive world. Code like

let didScroll = PushStream<CGPoint>()

extension MyClass: UIScrollViewDelegate  {
  func scrollViewDidScroll(scrollView: UIScrollView) {
    didScroll.next(scrollView.contentOffset)
  }
}

started emerging from every corner. Having a stored property, a delegate object and a method felt wrong. And it is. Switching between paradigms in multi paradigm code should be easier. If a delegate method is used to inform us about an event, it should be simple to convert invocation of that method to a stream event.

Message Forwarding

Cocoa and Cocoa Touch are written in Objective-C, the dynamically typed language. It means that the compiler doesn’t care much about types so you’re free to do wonders. iOS Developer Library puts it nicely:

Dynamic typing contrasts with static typing, in which the system explicitly identifies the class to which an object belongs at compile time. Static type checking at compile time may ensure stricter data integrity, but in exchange for that integrity, dynamic typing gives your program much greater flexibility.

That flexibility is what we are going to leverage here. When a message is sent to an Objective-C object, it goes through a number of steps before it’s handled or rejected. The runtime first searches for the method implementation that the selector refers to. If it is found, it will be invoked with the arguments contained in the message (if any), but if it’s not found, well, that’s where the party begins. The runtime will invoke forwardInvocation: method on the object, giving us the opportunity to handle the message by ourselves. It’ll wrap the message in an NSInvocation object that contains all information needed to handle it.

Invocation object contains the selector, method to read the passed arguments and a way to set the return value. NSInvocation is unfortunately not available in Swift so it needs to be handled in Objective-C subclass.

Let’s say an object is sent a message [person setAge:24]. If the object class does implemented method setAge: you’ll see the infamous unrecognized selector sent to instance exception. Reason you see that is because the default implementation of forwardInvocation: just invokes doesNotRecognizeSelector: which in turn throws an exception with that message. We can, however, override forwardInvocation: and handle the message differently. Let’s just print some info.

@implementation Person {

  - (void)forwardInvocation:(NSInvocation *)invocation
  {
    NSLog(@"Selector: %@", NSStringFromSelector(invocation.selector));
    NSLog(@"Argument count: %d", invocation.methodSignature.numberOfArguments);

    NSInteger arg1;
    [invocation getArgument:&arg1 atIndex:2];
    NSLog(@"First argument: %d", arg1);
  }

@end

If we try to run this, well, it’ll not work, not yet. For the runtime to construct the NSInvocation, in addition to knowing the selector, it needs to know the method signature. Method signature is just a fancy name for method argument and return types. Selector defines method name, whereas signature defines its types. To provide method signature to the runtime, we need to override another class method.

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
  return [NSMethodSignature signatureWithObjCTypes:"v@:i"];
}

NSMethodSignature can be constructed with a C-style string of type encodings for the method arguments. In this example, v stands for Void return type, @ represents self (the object) of type id, colon represents the selector and i represents the integer argument.

Don’t concern yourself about the details of type encoding right now as it is not important to solve our delegates problem. If you are interested, you can read more about it here.

You might be wondering why we had to specify the type for self and the selector. Well, Objective-C is implemented on top of C and the compiler is utilising C functions for method implementations. You can think of an object method like a function that is accepting the object itself as the first argument and the selector as the second argument followed by the actual method arguments.

void setAge(id object, SEL selector, NSInteger age)

If you’re interested in the details, Apple has a quick introduction to all this in their documentation.

Calling [person setAge:24] now will work and it’ll print

Selector: setAge:
Argument count: 3
First argument: 24

Notice that the first argument was read from index 2. I hope that the previous paragraphs made it clear why.

Parsing NSInvocation in Swift

As already mentioned, NSInvocation is not available in Swift, but we need to propagate it to Swift code that will send events to our streams. If you give it a bit thought, you’ll see that we don’t really need NSInvocation, but only parts of it. We need a selector and a way to read arguments. So let’s propagate just that.

We’ll start by adding another method to our class that will receive a selector and a block that can be used to read the arguments.

- (void)invoke:(SEL)selector argumentExtractor:(void (^)(NSInteger index, void *buffer))argumentExtractor
{
}

Also, we’ll update forwardInvocation to call that method.

  - (void)forwardInvocation:(NSInvocation *)invocation
  {
      [self invoke:invocation.selector argumentExtractor:^(NSInteger index, void *buffer) {
        [invocation getArgument:buffer atIndex:index];
      }];
  }

Next, we’ll subclass Person in Swift and override our invoke:argumentExtractor: method where we can handle the invocation.

class SwiftPerson: Person {

  override func invoke(selector: Selector, 
              argumentExtractor: (Int, UnsafeMutablePointer<Void>) -> Void) {
    let arg1 = UnsafeMutablePointer<Int>.alloc(1)
    argumentExtractor(2, a1)
    print("method \(selector) invoked with argument \(arg1)")
  }
}

First we allocate some space for our argument and then extract it with the closure (block) we’re provided. Trick here is that we have to know the type of the argument, in our case Int.

If we now call [person setAge:24] on an instance of SwiftPerson the console will print method setAge: invoked with argument 24. Calling [person thisIsSoCool:111] will print method thisIsSoCool: invoked with argument 111. Indeed it is!

From now on it should be straightforward how to generalise this approach to implement reactive delegates.

Generalising the Pattern

Let’s now see how this is used in ReactiveKit to implement reactive delegates.

Base Class

We have the type that can simulate any delegate. Its base is defined in Objective-C. We are simulating a protocol implementation (a delegate), so we need to know what protocol it is.

@interface RKProtocolProxyBase ()
@property (nonatomic, readwrite, strong) Protocol *protocol;
@end

@implementation RKProtocolProxyBase

- (instancetype)initWithProtocol:(Protocol *)protocol
{
  self = [super init];
  if (self) {
    _protocol = protocol;
  }
  return self;
}

...

Then we have an implementation of methodSignatureForSelector: method.

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
  struct objc_method_description description = protocol_getMethodDescription(self.protocol, selector, NO, YES);
  if (description.types == NULL) { 
    description = protocol_getMethodDescription(self.protocol, selector, YES, YES);
  }
  return [NSMethodSignature signatureWithObjCTypes:description.types];
}

We’re using Objective-C runtime to get the type encoding for a method of the given protocol. First we get method description by calling protocol_getMethodDescription with a protocol, method selector, boolean indicating whether the method is a required method or not and a boolean indicating whether the method is an instance method or not. We then instantiate NSMethodSignature with the type encoding contained in the description.

I’m confused why the optionality indication is needed to get the method description. It’s not possible to have two methods of the same name in the protocol, one optional and other not, so I’m not clear where the ambiguity would come from. Anyway, as we support both optional and required methods, we just try another if the first one fails.

Finally, we have an implementation of forwardInvocation: method which is same as in the introduction.

- (void)forwardInvocation:(NSInvocation *)invocation
{
  [self invoke:invocation.selector argumentExtractor:^(NSInteger index, void *buffer) {
    [invocation getArgument:buffer atIndex:index];
  } setReturnValue:nil];
}

- (void)invoke:(SEL)selector argumentExtractor:(void (^)(NSInteger index, void *buffer))argumentExtractor
{
}

Swift Class

Base class is subclassed in Swift.

public class ProtocolProxy: RKProtocolProxyBase {
  
  private typealias Extractor = (Int, UnsafeMutablePointer<Void>) -> Void
  
  private var invokers: [Selector: Extractor -> Void] = [:]
  private var streams: [Selector: AnyObject] = [:]

  public init(`protocol`: Protocol) {
    super.init(withProtocol: `protocol`)
  }

Dictionary invokers will contain selector-closure pairs. For a given selector, there will be a closure that updates a stream using the given argument extractor closure. Dictionary streams will store instances of PushStream type (publish subjects) that will be updated upon invocations of method for the given selector.

Don’t worry, everything will become clear in a moment.

Let’s begin with a method that provides a stream of invocations of a given selector.

  public func streamFor<T, Z>(selector: Selector, map: T -> Z) -> Stream<Z> {
    if let stream = streams[selector] {
      return (stream as! PushStream<Z>).toStream()
    } else {
      let pushStream = PushStream<Z>()
      streams[selector] = pushStream
      registerInvoker(selector) { a1 in
        pushStream.next(map(a1))
      }
      return pushStream.toStream()
    }
  }

First we check if we already have such stream and just return it if we do. Otherwise, we create it, store it in the dictionary, register an invoker that will update it and finally we return the stream. As a bonus we provide a mapping function that will convert the type because this is needed more often than not.

What about registerInvoker method? Well, here it is.

  private func registerInvoker<T, R>(selector: Selector, fire: T -> Void) {
    invokers[selector] = { extractor in
      let a1 = UnsafeMutablePointer<T>.alloc(1)
      extractor(2, a1)
      fire(a1.memory as T)
    }
  }

For the given selector we store a closure that will be invoked when the selector is performed. The closure will get an argument extractor it’ll use to read the argument. First it allocates a memory to store the read argument to, writes the argument to that memory address and fires a closure with the just written argument.

Only thing that’s left is to actually call those invokers. We’ll do that in an override.

  override func invoke(selector: Selector, argumentExtractor: (Int, UnsafeMutablePointer<Void>) -> Void) {
    guard let invoker = invokers[selector] else { return }
    invoker(argumentExtractor)
  }

And that’s it! Whenever a method that has a stream associated is invoked, we’ll send an event onto that stream with the mapped argument. Adding support for more than one argument should be obvious.

To fully cover our marks, we can also override following methods:

  public override func conformsToProtocol(`protocol`: Protocol) -> Bool {
    if protocol_isEqual(`protocol`, self.`protocol`) {
      return true
    } else {
      return super.conformsToProtocol(`protocol`)
    }
  }

  public override func respondsToSelector(selector: Selector) -> Bool {
    if streams[selector] != nil {
      return true
    } else {
      return super.respondsToSelector(selector)
    }
  }
}

Putting It All Into Practice

Now we can just instantiate a delegate object instead implementing a delegate protocol. We can use that object to make method invocation streams. For example:

// given
let scrollView: UIScrollView

// create a delegate
let delegate = ProtocolProxy(protocol: UIScrollViewDelegate.self)

// and set it
scrollView.delegate = delegate as! UIScrollViewDelegate

ReactiveKit provides NSObject extension that can make delegate creation even simpler:

let delegate = scrollView.protocolProxyFor(UIScrollViewDelegate.self, setter: NSSelectorFromString("setDelegate:"))

To get, for example, didScroll events, just take a stream for that delegate protocol method.

let didScroll: Stream<CGPoint> = delegate.streamFor(#selector(UIScrollViewDelegate.scrollViewDidScroll(_:))) { (scrollView: UIScrollView) -> CGPoint in
  return scrollView.contentOffset
} 

We’re probably interested in contentOffset of each scroll event so we’ll map scroll view into CGPoint.

I’d recommend wrapping all this in an extension of the class we’re delegate of to make things nicer.

extension UIScrollView {

  var reactiveDelegate: ProtocolProxy {
    return scrollView.protocolProxyFor(UIScrollViewDelegate.self, setter: NSSelectorFromString("setDelegate:"))
  }

  var didScroll: Stream<CGPoint> {
    return reactiveDelegate.streamFor(#selector(UIScrollViewDelegate.scrollViewDidScroll(_:))) { (scrollView: UIScrollView) -> CGPoint in
      return scrollView.contentOffset
    }
  }
}

Now, look at

scrollView.didScroll.observeNext { contentOffset in
  print("scroll view did scroll to \(contentOffset)")
}

and enjoy the beauty of it!

Conclusion

The implementation shown here is a stripped version from ReactiveKit. Check out this, this and this for full implementation with multiple arguments and methods with return values. Reactive delegates were released with ReactiveKit v2 so this is the first implementation. It could see some additions in future. For more info, check out the documentation.

Exploring Objective-C runtime and putting it to use these days when all the hype is focused on Swift was fun and educational experience. It reminded me how powerful dynamically typed languages can be and left me with a hope that Swift will see at least some of it in the future.

If you have any questions or suggestions around this feel free to leave a comment or contact me at @srdanrasic.

1: Not possible without Objective-C dependency, i.e. NSObject super-type.

comments powered by Disqus