Swift debug: Tips for developers Swift debug: Tips for developers
Skip to main content

Select your location

Man programming on a computer

Swift debug: Tips for developers

Seasoned Objective-C developers have numerous tips and tricks that they use to aid the debugging process. However, some of these don’t seem as fruitful or are simply not available in Swift and working in the console can sometimes feel clunky and laborious.

For example, you can po a variable from the console but the output is sometimes not ideal. Consider an arbitrary dictionary:

let dict: [String: Int] = ["apples": 3, "oranges": 5, "pears": 1, "bananas": 2]

Running po from the console outputs this:

▿ 4 elements
  ▿ [0] : 2 elements
    - .0 : "oranges"
    - .1 : 5
  ▿ [1] : 2 elements
    - .0 : "pears"
    - .1 : 1
  ▿ [2] : 2 elements
    - .0 : "bananas"
    - .1 : 2
  ▿ [3] : 2 elements
    - .0 : "apples"
    - .1 : 3

This rather hurts my eyes. Running p (which is shorthand for expression --) instead of po, yields something a little more readable:

([String : Int]) $R1 = 4 key/value pairs {
  [0] = (key = "oranges", value = 5)
  [1] = (key = "pears", value = 1)
  [2] = (key = "bananas", value = 2)
  [3] = (key = "apples", value = 3)
}

Compare this with the same command on the same object in the Objective-C, which is better still from a readability perspective:

{
    apples = 3;
    bananas = 2;
    oranges = 5;
    pears = 1;
}

One way of getting around this is, instead of using po <variable name>, you can use po print(<variable name>). This gives us the kind of same pretty looking console output that we’re used to:

["oranges": 5, "pears": 1, "bananas": 2, "apples": 3]

However, typing this all the time from the console will likely cause finger injuries. Luckily, we can add this command to our .lldbinit file, which LLDB reads at startup. You will find it in the home folder on your Mac (if it’s not there just create one). To do this we need the command regex command:

command regex dp 's/(.+)/po print(%1)/'

The command regex command allows the user to create powerful regular expression commands (regex) with substitutions. The regex and substitutions are specified using the format: s/<regex>/<substitution(s)>/. Input can be captured using parentheses in the regex. This input is then accessible via %1 for the first capture group and %2 for the second, etc.

In this case I’ve named my custom po command dp so, now I can just type dp <variable name> to get this behaviour. 

Inspecting the view hierarchy

I’ve become very fond of Chisel, a set of LLDB extension commands from Facebook. In particular there are some extremely useful commands that I use all the time in Objective-C like pviews <view variable name>, which prints the view hierarchy for a given view (or the entire view hierarchy when run without a view as an argument), e.g:

<UIWindow: 0x7fae4364c680; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x7fae4363a8f0>; layer = <UIWindowLayer: 0x7fae4364b460>>

   | <UITransitionView: 0x7fae43471bf0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fae43471920>>

   |    | <UILayoutContainerView: 0x7fae4346c780; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x7fae4346eed0>; layer = <CALayer: 0x7fae4346c910>>

   |    |    | <UINavigationTransitionView: 0x7fae4346d450; frame = (0 0; 375 667); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x7fae4346e520>>

   |    |    |    | <UIViewControllerWrapperView: 0x7fae436848f0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fae4366faf0>>

   |    |    |    |    | <UIView: 0x7fae43524790; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fae43519920>>

   |    |    |    |    |    | <UITableView: 0x7fae44811000; frame = (0 64; 375 603); clipsToBounds = YES; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x7fae4352ce30>; layer = <CALayer: 0x7fae43521880>; contentOffset: {0, 0}; contentSize: {375, 2676}>

   |    |    |    |    |    |    | <UITableViewWrapperView: 0x7fae44817400; frame = (0 0; 375 603); gestureRecognizers = <NSArray: 0x7fae43508480>; layer = <CALayer: 0x7fae43529ea0>; contentOffset: {0, 0}; contentSize: {375, 603}>

   |    |    |    |    |    |    |    | <UITableViewCell: 0x7fae436b0b70; frame = (0 568; 375 44); text = 'RockZone - The Bipolar Se...'; autoresize = W; layer = <CALayer: 0x7fae436b0b10>>

...

And pvc which prints the current view controller hierarchy, e.g:

<BaseNavigationViewController 0x7fd9fb8d5000>, state: appeared, view: <UILayoutContainerView 0x7fd9fcc471c0>

   | <FirstViewController 0x7fd9fcc36720>, state: disappeared, view: <UIView 0x7fd9fb4e9850> not in the window

   |    | <FirstChildViewController 0x7fd9fcbb5d90>, state: disappeared, view: <UIView 0x7fd9fcbd1a50> not in the window

   | <SecondViewController 0x7fd9fba05a00>, state: appeared, view: <UIView 0x7fd9fce4c3a0>

   |    | <SecondChildViewController 0x7fd9fce9a2a0>, state: disappeared, view: <UIView 0x7fd9fcea8480>

However, many users are finding these commands either misbehave or don’t work at all in a Swift context.

These two commands in particular access private APIs behind the scenes such as -[UIView recursiveDescription] and +[UIViewController _printHierarchy], which are not available when stopped in a Swift frame in the debugger.

In the Advanced Debugging In Swift WWDC 2014 session, they offered some clues as to how we might get around this by telling LLDB to use Objective-C when evaluating an expression: expr -l objc++ -O.

As you can imagine, -l objc++ forces the use of Objective-C, while the -O flag stands for 'object description'. We can now use a command like expr -l objc++ -O -- (id)<object pointer> to print an Objective-C style description of an object in the console (an alternative to the method outlined above). And, because we're telling LLDB to use Objective-C, we can use all the private methods we want.

So, this could also be extended to create our own version of pviews:

expr -l objc++ -O -- [(id)<object pointer> recursiveDescription]

Again this could be placed in our .lldbinit file for easy use:

command regex spv 's/(.+)/expr -l objc++ -O -- [(id)%1 recursiveDescription]/‘

Here I’ve called our custom pviews command spv. The only caveat is that we need to run a prior command at the console in order to get the pointer address to pass into spv.

As for pvc (see above), that doesn’t seem to work at all in Chisel 1.2.0, which is the latest version as of this writing. This is a shame because it can be a real time saver, particularly when starting on new codebases.

Previously you could simply run the app, hit pause on the debugger in Xcode and type pvc from the console and out pops a visual depiction of the current view controller hierarchy! Luckily we can easily add another entry in our .lldbinit file to make this work:

command alias spvc expr -l objc++ -O -- [UIViewController _printHierarchy]

Note that we can use command alias as opposed to command regex, which basically allows us to do more complex commands using a ‘shortcut’ command; spvc in this case. Now doing spvc gets the same awesome output from a Swift frame.

Data Formatters

Swift uses data formatters to print a description of a variable in the console as opposed to Objective-C’s use of -[NSobject description], which is why we see the output we do when poing the contents of a dictionary in the console in Swift, for example. However, LLDB does allow you to add your own data formatters by using the type summary add command. So you can pretty up that data to your heart’s content. Here's a contrived example with an ‘address’ struct:

struct Address {

  var recipient: String

  var buildingName: String

  var streetName: String

  var city: String

  var postCode: String

  var country: String

}

let address = Address(recipient: "Kin + Carta", buildingName: "The Spitfire Building", streetName: "Collier Street", city: "London", postCode: "N1 9BE", country: "United Kingdom")

Now p address from the console outputs:

(TestApp.Address) $R1 = (recipient = "Kin + Carta", buildingName = "The Spitfire Building", streetName = "Collier Street", city = "London", postCode = "N1 9BE", country = "United Kingdom")

po address is a little better in this case:

Address

  - recipient : "Kin + Carta"

  - buildingName : "The Spitfire Building"

  - streetName : "Collier Street"

  - city : "London"

  - postCode : "N1 9BE"

  - country : "United Kingdom"

But we could go one better by rolling our own data formatter. With the app stopped in the debugger, we could do:

type summary add -s "\n${var.recipient},\n${var.buildingName},\n${var.streetName},\n${var.city} ${var.postCode},\n${var.country}." TestApp.Address

This binds the formatting inside the quotes ("") to the type at the end (TestApp.Address). Note that it’s necessary to include the module name prefix of the type when adding the data formatter. You can reference members of the variable with the special ${var.<member name>} syntax. Notice the selective interspersing of spacing, commas and new line characters to make this format more like a real address. Now when we do p address, we get:

(NewReleases.Address) $R4 = 

"Kin + Carta",

"The Spitfire Building",

"Collier Street",

"London" "N1 9BE",

"United Kingdom".

The Advanced Debugging in Swift session discusses printing the output of these custom formatters using the po command. However, I needed to use the p command. Alternatively, you could use fr v (frame variable). Indeed the docs for type summary add suggest this is the way it needs to be.

The cool thing is that you can traverse relationships so you could print more complex descriptions. Let’s assume that recipient in the Address struct was itself another struct such as:

struct Recipient {

  var name: String

}

Then the data formatter could be created by traversing the relationship with type summary add -s "\n${var.recipient.name},...

These data formatters will be available across runs of your application. However, because the version of LLDB that comes with Xcode doesn’t look in the working directory for an .lldbinit file, they would need to be added again after restarting Xcode. Of course, you could put these commands in your .lldbinit file but that isn’t really the place for project specific commands.

Alternatively, you could create a separate file (.lldbcommands, for example), which adds your data formatters (or any other project specific LLDB commands), which you then load using the command source <file path> LLDB command. Simply add a breakpoint that auto-continues in applicationDidFinishLaunching() that runs this command and you’ll have all your favourite data formatters ready to go.

Otherwise, you could check out this interesting looking Xcode plugin, which seems to solve this very problem (not tested). If you're really eager you can even configure this stuff using Python scripts. Check out the docs with help type summary add.

Swizzling

Swizzling can be a very useful method of debugging in situations where the code you want to inspect is not reachable because you want to check something on a class that is owned by the frameworks or is too deep down in your own framework, for example.

Want to see what’s happening when addSubview() gets called on views that are owned by framework classes? Swizzling that method on UIView may be able to help. What I think some people don’t realise is that swizzling is not just an Objective-C luxury.

Every Swift app runs inside the Objective-C runtime, making swizzling a possibility since it’s all runtime methods that enable the swizzling magic to happen. Nate Cook has a really interesting article on how to achieve this including some tips and caveats. For example, NSObject.load(), a favourite place for developers to set up method swizzling, doesn’t get called in Swift so, it’s necessary to utilise NSObject.initialize(). But swizzling is still possible on pure Swift objects by simply decorating methods with the dynamic modifier.

Swift REPL (Read Eval Print Loop)

Lastly, the Xcode command line tools come with the Swift REPL, which is a way to interactively test your code, directly from the terminal. You can experiment with the syntax, define variables, classes and any other Swift type, run your code and view its output which can be handy for getting a quick view for how something might work. However, in practice, I suspect Playgrounds would be the preferred choice.

If you want to continue the conversation about Swift debug tweet us @kinandcarta.

 

Interested in learning more?

Get in touch