This blog post introduces a simple pattern for adding multicasting capabilities to existing iOS controls. Adding multicasting allows for improved clarity and code re-use.
Most iOS controls have a concept of a 'delegate' - a protocol which is used to handle various user interactions and control state changes. If, for example, you want to detect when a
UIWebView starts to load a web page, you set the
delegate property to a class which adopts the
UIWebViewDelegate protocol, and implement the
webViewDidStartLoad: method. This works well in practice for simple cases, the fact that each control has a single associated delegate is quite limiting. There are various reasons why you might want to handle delegate messages, and naturally you might want to handle these in different parts of your code in order to promote code re-use. For this reason, a multicasting delegate would be a much better option.
The runtime behaviour of Objective-C is based on message passing between object instances. In most cases message passing is equivalent to invoking a method directly n the target object, however, it gives us the opportunity to re-route messages to one or more targets.
Image courtesy of kainet, used under Creative Commons ShareAlike license
In a recent article for Ray Wenrderlich's site, I demonstrated how
forwardingTargetForSelector: could be used to forward messages from one object to another. The code below shows how a class can handle
UIScrollViewDelegate messages, whilst forwarding these messages to a 'chained' delegate:
For more information on this example, see the original article.
Message forwarding allows you to handle messages sent to the delegate, forwarding them to another delegate implementation, resulting in an implementation that follows the Proxy Pattern. This does allow for multiple delegate implementations, but the use of chaining rather than multicasting is quite messy.
So, what's the alternative? Fortunately Objective-C provides a more generic mechanism for processing messages by implementing
forwardInvocation:. Using this to create a multicasting concept is really quite simple, so we'll just dive right into the code for a class that provides this functionality. The interface of this class simply allows the user to add multiple delegates:
The implementation stores these delegates in an array. Whenever a message is sent to the
SHCMulticastDelegate it determines whether the delegates can handle this message via
respondsToSelector:, if so,
forwardInvocation: iterates over the delegate using the supplied
NSInvocation instance to forward the message to each of these delegates.
The implementation of
methodSignatureForSelector: is required by
forwardInvocation: as part of the standard forwarding procedure.
NOTE: This implementation does nothing to check that each of the supplied delegates conform to the same delegate protocol. It could certainly be made more robust!
In practice, this class can be used as follows:
In the above code, each of the delegates that were added to the
SHCMulticastDelegate will be informed when
UITextViewDelegate messages are sent.
This implementation looks pretty good, but there is still room for improvement. In the above code, any class that wishes to handle message sent to the delegate must obtain a reference to the SHCMulticastDelegate instance. It would be much better if the multicasting capability could be added to the
Categories to the rescue ...
The following category adds a property to
Typically you would use an instance variable to 'back' a property, however, you cannot add instance variables to class using a category. Fortunately, Objective-C provides a way to associate data with an object using string keys. See the use of
objc_getAssociatedObject in the code below:
With the above code
UITextView now has multicasting capabilities built in. We'll look at how this category can be used in a slightly more in-depth example.
A common use of the
UITextViewDelegate is to prohibit newlines in a multi-line text input. Typically this would require pasting the standard code required into the class which handles the delegate. Here we'll see how multicasting can be used to make this code more easily re-used.
The following class handles the delegate in order to provide the hide-keyboard-on-enter behaviour:
The implementation is pretty trivial:
This behaviour can now be added to any
UITextView without interfering with application-specific logic. For example, this simple view controller also handles the
UITextViewDelegate in order to display a character count:
The above example was deliberately selected because the implementation of this 'behaviour' is quite simple. For a more complex example, take a look at the 'Clear Style' project I have over on github. Here I use multicasting in order to add multiple pull-to-add-new behaviours to a list, where each behaviour handles the same delegate methods.
Finally, you might have noticed that the
UITextViewHideKeyboardOnEnterBehaviour implementation above handles a delegate method with a non-void signature. Despite the fact that we are multicasting these messages return values are still possible! I must admit I could not find any information about how the runtime handles return values when
invokeWithTarget: is used to invoke multiple targets. Through testing I have discovered that the value returned by the last invocation is the one that is ultimately returned. I would certainly recommend caution if you plan to use this feature!
You can download the code for the simple example here: MulticastDelegates.zip
Regards, Colin E.