Blockception
08 Dec 2012Blocks are a lot of fun and they can make some really slick API’s. It’s also worth noting that blocks also require a fair amount of overhead to get used to how they work and how to use them. I would consider myself fairly competent with blocks but this confidence can lead to potentially “clever code”. “Clever code” is sometimes hard to maintain depending on how clever you was feeling at the time of writing it.
In this post I intend to cover one such bit of “clever code” and how it could be implemented differently to compare and contrast the results.
##Using delegates
I’ll start by looking at the implementation I didn’t actually do and then step by step arrive at the solution I initially wrote. From here I can compare and contrast and see which solution I should have done.
The sequence of events looks something like this
So at this point we have a working solution and all is good.
The code to loop over the points looks something like this
View.m
Something about asking for the count and then iterating over it seems a little awkward to me, I’d much rather the thing that held this information (the model) kept it’s data a little closer to it’s chest and iterated over itself passing out the points. There are two options here
- Implement
NSFastEnumeration
- Add some kind of enumerator
I prefer the second option
##Block enumeration
The block enumeration ends up looking like this:
Model.m
This creates an issue - for this to work I would have to call this enumerator in the View
. I could simply bypass the Controller
and pass the View
a reference to the Model
but I don’t like the sound of this. The more pressing issue is that the Model
works in a different coordinate space to the View
and the Controller
is currently handling this conversion.
To get this to work I’ll need to restructure to look like this
This diagram actually looks simpler. There’s a couple of things to now note
- There are new required methods that need to be called and in a specific order
beginDrawing
andcommitDrawing
- The path building now occurs over a series of method calls to
View
Straight away I can see a way to remove the requirement to call beginDrawing
and commitDrawing
at this level by using a block that wraps this up in a tasty sandwich.
##Sandwich block
The sandwich block puts the interesting code that changes as the sandwich filler and the boilerplate as the bread, which will look like this:
Model.m
This hides a message in our sequence diagram and removes the requirement to remember to call beginDrawing
and commitDrawing
.
So actually getting the View
to draw can now all be kicked off from the Controller
and would look something like this:
Controller.m
Now at this point we can look at the public interfaces of the MVC trio and it’s looking quite smart
Model.m
Controller.m
View.m
Every object appears to be toeing the MVC line, although I see a method that probably doesn’t belong in the public API. The View
now has the method -[View buildWithPoint:]
, which only makes sense in the context of drawing, it’s not clear by looking at the public interface what this method does or in what context to call it.
So here’s another opportunity to use a block, this final implementation brings us to the title of this post Blockception
. We now end up with a block in a block which calls a block passed in by the first block.
This ends up looking like the following:
View.h
Controller.m
The drawBlock
essentially takes the functionality of the old -[View buildWithPoint:]
and passes it straight where it is actually needed.
##Differences
###Delegate Implementation
The delegate implementation requires a fair amount more code to write. A @protocol
needs to be introduced to allow the view to have a dataSource
. There is also the methods on the controller that conform to this protocol and end up proxying them straight onto the Model
and then slightly changing the result.
Looking at the initial sequence diagram there seems to be more back and forth of messages to achieve the same result than where we end up.
The Model
is required to expose how many elements it has and then allow an external object to iterate over that in a way which is out of it’s control.
###Block Implementation
The block implementation has 2 of my favorite ways to use blocks, which are for enumeration and wrapping code.
The public interfaces for all the objects expose very little about themselves, which is always nice.
There are considerably more awkward carets and curly braces, which can be confusing.
##Conclusion
Looking back at both solutions the delegate technique can be easier to fully grasp and follow along. The block implementation completely failed my “can I explain how this is working to a colleague in one attempt” rule, but I feel the delegate setup would only fair slightly better.
The reason I originally implemented this using blocks over delegates was purely because I had the block enumeration on the Model
and this was the only way I could think to make it all fit.
I do like how the block implementation hides away any gory details about the structures of the objects but the very fact that I’ve had to write a blog post about this probably means it’s too clever. I think the answer to “which is better?” is we’ll have to wait and see how the block implementation stands up over time.