Leading on from Chris’ Intention post, I continued the journey to make my controllers slimmer.
If you haven’t read the post, please do because the things that I’ll share are based on that. It explains about how to make your controllers as light as possible. So how?
- Code one and only one functionality as one Intention class
- Drag an
NSObject in the storyboard, make it as the custom Intention class
- Wire up your controllers to the classes
The fun thing is that you can add features and functionalities in your controller by wiring things up in Storyboard. One can ‘code’ the controller’s functionalities without writing actual codes.
However, I encoutered a problem when working with delegates. An object could only have one delegate object. Delegate objects have compulsory and optional methods that should be implemented in one class that conform to the protocol. Usually each methods has different functionality. If we implement all of them in one class, it will violate the concept of SRP.
I asked Chris himself about the issue, his answer pointed me about multicast delegate.
With a little bit of google research, I found an article and a sample code that explain the techniques involved.
But still, I’m not satisfied. I need a slightly different direction on the implementation. Since using Intention relies so much on storyboard already, I want to able to wire my object to its delegates using Interface Builder rather than using written code. Also, I need all of the delegates’ methods to be called, even for the ones that returns a value.
textFieldShouldReturn: for example. The code should traverse all the delegates and check whether it will respond to the method. If it can, then it’s safe to call the method. It should return
NO if there’s one delegate that returns
NO. Otherwise, it should return
'Proof of Concept' sample app
So here’s how I went with the idea of extending the concept of Intention with delegates. The objective is to be able wire many functionalities as a delegate object.
I made an app to prove that it’s feasible. The app’s pretty trivial. It’s an app with a text field that has three features;
- dismisses when return key is pressed
- limits the characters allowed
- enable a button if the text is a valid email
The controller in this app is really light. It consists of a text field and an implementation of
viewDidAppear:. I need both to make the text field the first responder after the controller appears.
It’s the same concept as the one in the original post. The only thing that’s different from the original is instead of using
textField:shouldChangeCharactersInRange:replacementString:, it uses
The cool thing is that we can set the maximum characters from storyboard with user defined runtime attributes. If not set, then we put a constant with a reasonable value (ex. 30 characters).
First, we should have an NSString category for validating email. Next, we need to define how to communicate with external object such as
UIButton. I copied the technique from the original post by making an
IBOutlet for the target. Make an outlet from this class to the button and set the keypath in user defined runtime attributes.
So how to forward all the delegate calls from a
UITextField? The important thing that I learnt from the article is to have all the calls be forwarded to all delegates in the array.
I chose with subclassing instead of creating a separate MulticastDelegate object (like one in the article). I didn’t use
forwardInvocation: but I just implemented necessary methods and make sure that all of the delegates is called. One thing to note, we should check first if it responds to the selector before make the actual call.
We need Storyboard to recognize our array of property. We do this by adding
IBOutletCollection to our property of
delegates. And don’t forget to switch the text field’s class to your subclass (mine is
Why not use category? In my opinion, subclassing is safer and more proper since we need to add a property1. With this approach, we’re not changing anything with the original
Wire Things Up
Check the animation to see how the wires are connected. So we’re adding features only by wiring things. Simple. 👌
One caveat is that IBOutletCollection can’t handle id objects with protocols (ex.
id<UITextFieldDelegate>). Since we already defend our code to only call methods from a delegate object that can respond to a selector, then everything should be fine.
Check out the Code
I’ve uploaded to Github for anyone who is interested; extending my intention.
To Sum Up
The concept of separating controller’s code to pieces of intention class could still be improved even more. The class would turn into bits which we can use by wire them up. With this, not only our controller’s code is light, but all of our class still conforms to SRP.