I’m not a fan of the development cycle on iOS. The delays caused by recompilation and app launch are so frustrating that I try go through this tedious process as rarely as possible. Obviously, I still have to get some work done, so lately I’ve found myself using two tools indispensable in the course of making my life easier. I use both LLDB and Reveal to call arbitrary methods at runtime.
Calling a method requires having access to the target, usually in the form of an address representing the memory location of an object. This begs the question: how can we easily obtain an address when the application is running?
Getting the Address
Getting the memory location of an object is as easy as placing a breakpoint and getting the value straight from the variables preview. That approach, however, is not very convenient. Being forced to stop the execution of our program just to fetch the address is way too much hassle.
To get rid of the laborious “pause on breakpoint – read value – continue” cycle we could pollute the code with calls to
NSLog(@"Address is %p", object);
Fortunately, there is a cleaner way. We can edit the breakpoint workflow by adding a print action and forcing a breakpoint to “Automatically continue after evaluating”:
This doesn’t solve the issue completely. What if I want to get the address of a specific
UITableViewCell? Putting a breakpoint in
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath; would trigger the breakpoint multiple times, once for each cell. Granted, we could modify the breakpoint condition as well, ensuring that the cell’s address gets printed only for a specific
NSIndexPath, but that’s already overkill for getting the value of a single memory location. Moreover, the breakpoint method won’t work for UIKit classes, where we don’t have straightforward access to the object’s properties and ivars anyway.
This is where Reveal comes into play. Having the visual representation of the view hierarchy mapped to the underlying objects is extremely useful. The killer feature for me is the fact that the Itty Bitty Apps kindly shared with us the address of the inspected view:
Making Use of the Address
What can we use an object’s address for? Direct execution of arbitrary methods, for one. By pausing the app and typing one-line commands into LLDB you can literally call whatever method you want.
The most useful methods to call are public methods. Modifying an object at runtime, without writing any code, is surely a useful capability to have:
e [(UIView *)address setNeedsLayout];
Change the value of a custom property inside the
e [(ChartView *)address setLineStrokeColor:[UIColor redColor]];
Reload that pesky
e [(UICollectionView *)address reloadData];
I’m a huge fan of iOS Runtime Headers. Peeking into the list of methods a given class implements is eye-opening and makes rapid prototyping much easier. While the referenced repo is the definite guide on hackery, the following two methods make coding much more flexible:
Get quick access to the nearest
UIViewController for a given view
po (UIViewController *)[(UIView *)address _viewControllerForAncestor];
Does this control have any targets?
e (BOOL)[(UIControl *)address hasOneOrMoreTargets];
A Quick Summary
While this method works great for the visual side of your application, Reveal won’t help you get the addresses of your model objects, so in this case you’ll probably have to use breakpoints or logging.
For the UI work, the LLDB-Reveal combo is a great tool that makes rapid iteration much more pleasant.