Blockception

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

Delegate Implementation

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 (int i = 0; i < self.dataSource.numberOfPoints; i++) {
  CGPoint point = [self.dataSource pointAtIndex: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

  1. Implement NSFastEnumeration
  2. Add some kind of enumerator

I prefer the second option

##Block enumeration

The block enumeration ends up looking like this:

Model.m

- (void)enumeratePointsWithBlock:(void (^)(CGPoint point, NSUInteger idx, BOOL *stop))block;
{
  BOOL exitLoop = NO;
  
  for (int i = 0; i < self.locationCount; i++) {
    block(CGPointMake(self.locations[i], self.locations[i]), i, &exitLoop);
    if (exitLoop) {
      break;
    }
  }
}

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

Enumeration Implementation

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:

Model.m

- (void)drawWithBlock:(void (^)(void))block;
{
  [self beginDrawing];
  block();
  [self commitDrawing];
}

This hides a message in our sequence diagram and removes the requirement to remember to call beginDrawing and commitDrawing.

Sandwich Implementation

So actually getting the View to draw can now all be kicked off from the Controller and would look something like this:

Controller.m

- (void)drawGraph;
{
  [self.view drawWithBlock:^{
    [self.model enumeratePointsWithBlock:^(CGPoint point){
      [self.view buildWithPoint:[self convertPoint:point]];
    }];
  }];
}

Now at this point we can look at the public interfaces of the MVC trio and it’s looking quite smart

Model.m

- (void)enumeratePointsWithBlock:(void (^)(CGPoint point, NSUInteger idx, BOOL *stop))block;

Controller.m

- (void)drawGraph;

View.m

- (void)drawWithBlock:(void (^)(void))block;
- (void)buildWithPoint:(CGPoint)point;

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

typedef void (^draw_point_b)(CGPoint drawPoint);

Controller.m

- (void)drawGraph;
{
  [self.view drawWithBlock:^(draw_point_b drawBlock) {
    [self.model enumeratePointsWithBlock:^(CGPoint point){
      drawBlock([self convertPoint:point]);
    }];
  }];
}

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.

Simple Developer Happiness gains

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.

some-where-in-$PATH/xopen

#!/usr/bin/env ruby

require 'shellwords'

proj = Dir['*.xcworkspace'].first
proj = Dir['*.xcodeproj'].first unless proj

if proj
  puts "Opening #{proj}"
  `open #{proj}`
else
  puts "No xcworkspace|xcproj file found"
end

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.

Getting interactive with the debugger

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:

(lldb) po [UIFont fontNamesForFamilyName:@"Chalkboard SE"]
(id) $1 = 0x06a84670 <__NSCFArray 0x6a84670>(
ChalkboardSE-Regular,
ChalkboardSE-Bold,
ChalkboardSE-Light
)

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

###Scalar values

If in my program I had

-[SomeAwesomeViewController viewDidAppear:]

- (void)viewDidAppear:(BOOL)animated;
{
  CGFloat   someFloat   = 5.f;
  NSInteger someInteger = 10;
  
  ...
}

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:

-[SomeAwesomeViewController viewDidAppear:]

- (void)viewDidAppear:(BOOL)animated;
{
  NSString *startDate = @"2012-09-15";
  
  NSDate *date = [self.someDateFormatter dateFromString:startDate];
  
  ...
}

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…

(lldb) expr (void)[self.someDateFormatter setDateFormat:@"yyyy-MM-dd"]
<no result>

Sweet this didn’t error so let’s confirm it worked

(lldb) po [self.someDateFormatter dateFormat]
(id) $2 = 0x06a88430 yyyy-MM-dd

Now let’s try it out on our startDate

(lldb) po [self.someDateFormatter dateFromString:startDate]
(id) $7 = 0x06d7d130 2012-09-15 00:00:00 +0000

Excellent, jobs a gooden.

###More awkward calls

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

(lldb) command alias runloop expr (void)[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:(NSDate *)[NSDate date]]

Now calling runloop should spin the runloop once for me resulting in a screen update

Now let’s have a real play:

Without rebuilding - I want to create a label, change some settings and then finally give it some text.

####1. I need to get a frame:

(lldb) expr (CGRect)NSRectFromString(@"60 100 200 100")
(CGRect) $1 = origin=(x=60, y=100) size=(width=200, height=100)

####2. Now I need a label, with that frame

(lldb) po (id)[[UILabel alloc] initWithFrame:(CGRect)$1]
(id) $2 = 0x06b533a0 <UILabel: 0x6b533a0; frame = (60 100; 200 100); clipsToBounds = YES; userInteractionEnabled = NO; layer = <CALayer: 0x6b46af0>>

####3. Let’s change the background colour to black

(lldb) expr (void)[(id)$2 setBackgroundColor:(id)[UIColor blackColor]]
<no result>

####4. Let’s make sure the text is readable by setting it’s colour to white

(lldb) expr (void)[(id)$2 setTextColor:(id)[UIColor whiteColor]]
<no result>

####5. Let’s add this to the view hierarchy

(lldb) expr (void)[self.view addSubview:(id)$2]
<no result>

####6. Now I’ll spin the runloop to check my handy work

(lldb) runloop
<no result>

Attempt 1

####7. Ooops let’s add some text and spin it again

(lldb) expr (void)[$2 setText:@"Awesome"]
<no result>
(lldb) runloop
<no result>

Attempt 2

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.