Blocks 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
for(inti=0;i<self.dataSource.numberOfPoints;i++){CGPointpoint=[self.dataSourcepointAtIndex:i];// convert point and build path}
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
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 and commitDrawing
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:
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.
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.
Doing repetitive work over and over is always frustrating. Thankfully good developers automate the things they do or find other people who have done the hard work already.
You don’t have to be amazing at scripting to hack something together that can really make things easier. People often stray away from tech they are not used to but it’s worth just “giving things a go” to see how you get on.
Here’s a simple Ruby script that I use several times a day when working with my Xcode projects.
##The problem
I often start from or find myself on the command line using Git or navigating my projects. I also like to use CocoaPods to deal with project dependencies. This results in an annoying issue when opening Xcode, for projects that use CocoaPods you need to open the *.xcworkspace and for projects that don’t use CocoaPods you need to open *.xcodeproj. I also do not enjoy using the graphical File->Open….
##The solution
Make a little script to deal with the inconsistency and allow me to open projects quickly form the command line.
The key to this script is that I don’t get bogged down with details of how to implement it perfectly. It does exactly what I need and should be easy to follow at a later date if I need to change anything.
##Further notes
The initial version of this script had a potential floor which was pointed out by my colleague Oliver Atkinson. In the first instance I didn’t use shellwords to escape the project name. This causes an issue if the name has a space in it, which had never affected me personally as I always camel case my project names, but it’s an edge case that will be hit often when sharing code with others.
The main take away from this learning is that I didn’t need to spend hours making the script perfect (and I’m sure it still isn’t) as it suited my requirements - as soon as the requirements change it’s time to fix it up and make it work.
One of the cool things about RubyMotion is that when you run your app you get a REPL (read, eval, print, loop) that you can quickly try some code out in. Although it’s not as easy to use, the debugger in Xcode (I’m using lldb) can give you some of the same experience when it comes to interrogating objects and trying basic things.
Here are some simple examples of techniques to use to help you out:
##Interrogation
I can never remember how I’m supposed to spell font names when using +[UIFont fontWithName:size:], so I’ll often just set a break point anywhere to get me into the debugger and then ask UIFont:
(lldb) po [UIFont familyNames]
(id) $1 = 0x06a9e450 <__NSCFArray 0x6a9e450>(
Thonburi,
Snell Roundhand,
Academy Engraved LET,
... snip
Oriya Sangam MN,
Didot,
Bodoni 72 Smallcaps
)
This is cool but I’m lazy and I want this output sorted so I don’t have to scan up and down
(lldb) po [[UIFont familyNames] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]
(id) $2 = 0x06aa28f0 <__NSArrayI 0x6aa28f0>(
Academy Engraved LET,
American Typewriter,
Apple Color Emoji,
Apple SD Gothic Neo,
... snip
Verdana,
Zapf Dingbats,
Zapfino
)
Now that’s much easier to look at.
Just a quick note there are a couple of ways I could have done this. I used [UIFont familyNames] nested inside the sortedArrayUsingSelector: method, but I could have just as easily used
The memory address of the originally returned array [0x06a9e450 sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]
The variable that the result of the first expression was assigned to [$1 sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]
Now I’m deeply saddened that Comic sans is not in this list but I can see something that sounds equally as awesome “Chalkboard SE”. So to answer my original question and find out what I need to use in my +[UIFont fontWithName:size:] I will again ask UIFont:
Excellent now I know the exact string I need to be using for the different weights.
Make sure you see beyond the example above and the see the power of interrogating objects and calling their methods.
##Changing values
Sometimes you want to just jump in and see how changing a value effects something without rebuilding. Again you can actually do a fair amount in the debugger
I can add a breakpoint just after the assignments and then change both of these values using expr
expr someFloat = 4.32
expr someInteger = 20
To confirm the change I can inspect with
(lldb) p someFloat
(CGFloat) $1 = 4.32
(lldb) p someInteger
(NSInteger) $2 = 20
###Objects
I can also manipulate objects the same way
If I have the following code, which currently doesn’t work because the dateFormatter has the wrong format. I can find out what the format is and play around until I get the correct result:
I add a breakpoint after the startDate is declared and then play. This is where we start to see that it can be a little tricky to make lldb do what we want
###1. Find out what the current dateFormat is
(lldb) po self.someDateFormatter.dateFormat
error: property 'dateFormat' not found on object of type 'NSDateFormatter *'
error: 1 errors parsing expression
So above we can see that dateFormat needs to be called as a method, :( no dot syntax this time…
(lldb) po [self.someDateFormatter dateFormat]
(id) $1 = 0x06d79cd0 dd/mm/yyyy
OK so we can now see why this is not working as the dateFormat is completely wrong for the input string. Let’s try a different format
(lldb) expr [self.someDateFormatter setDateFormat:@"yyyy-MM-dd"]
error: no known method '-setDateFormat:'; cast the message send to the method's return type
error: 1 errors parsing expression
Mmmm I scratch my head and then try doing what it asks by adding a (void) cast on the return…
You’ll find that you actually need to cast a lot so just be very aware of that.
Here’s a couple examples of that
(lldb) p [self.view bounds]
error: no known method '-bounds'; cast the message send to the method's return type
error: 1 errors parsing expression
(lldb) p (CGRect)[self.view bounds]
(CGRect) $9 = origin=(x=0, y=0) size=(width=320, height=460)
(lldb) expr (void)[self.view setBackgroundColor:[UIColor redColor]]
error: no known method '+redColor'; cast the message send to the method's return type
error: 1 errors parsing expression
(lldb) expr (void)[self.view setBackgroundColor:(UIColor *)[UIColor redColor]]
<no result>
(lldb) expr (void)[self.view setBackgroundColor:(id)[UIColor redColor]]
<no result>
##Experimentation
Now I have no idea if this is useful/safe but you can actually mess around fairly freeform
I’ll start off by making a command alias to run the runloop
Now I actually have a label on the screen with my text in it, without leaving the debugger.
##Conclusion
The examples provided here are fairly contrived, but the take away is that if you get familiar with the debugger you can actually do some cool experimentation. Here I’ve shown how to do some basic interrogation of objects without adding NSLog()’s and rebuilding, which is pretty cool. I’ve certainly had occasions where I’ve had a designer stood over my shoulder to tweak some UI and I would have loved to not have to rebuild repeatedly while we both wait just to check that the pixel adjustments were correct, especially when the view being edited was deep in a navigation stack and required a lot of clicking.