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

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.