Summit carola cover?fm=jpg&fl=progressive&q=75&w=300

Debugging in Swift: How Hard Can It Be?

Why is debugging in Swift so much more frustrating than debugging in Objective-C? Expanding on her recent experience of using Swift in production, Carola Nitz shares some of the debugging techniques she’s had to discover along the way. We learn about breakpoint settings, debugging those cryptic Swift compiler errors, launch arguments, and a range of useful tools that should make life a little easier.


Why Do We Struggle? (0:00)

I work at nxtbgthng, and that’s where I got the chance to work on my very first Swift app. As you might imagine, I struggled a lot with debugging in Swift. Apparently I’m not the only one, so I decided to share my experiences — how hard can it be to debug in Swift?

As it turns out, it is hard, really hard. Why do we struggle more with debugging in Swift than in Objective-C? One reason is because the output is not helpful. We’ve all tried to use po and LLDB to look at an Objective-C object, only to get an unhelpful LLDB message giving us the address of said NSObject. The error messages also make no sense. For example, I tried to return a CGPoint with a random integer, and the incomprehensible error was, “Cannot invoke ‘init’ with an argument list of $T4 or $T9”. Third, we just don’t know what debugging tools we have.

LLDB (1:35)

The first topic is the low-level debugger. Those of you who know a bit more about LLDB might know that po is actually an alias for expression -O -- objectiveCString. This might cause you to wonder, “What other options are available when I want to use expression?”. The option -l allows you to tell expression that you are giving it an Objective-C object. You can then use expression -l objc -- with the address of the Objective-C string to finally get the description. This was really helpful for me these past few months; it can save you from a lot of println debugging.

Breakpoints (2:49)

LLDB is great if we already have the object that we want to look at, but what if we haven’t already pinned down the object to stop at? To cut down on the time spent during debugging, it will be helpful to know more about breakpoints.

Get more development news like this

To get more out of breakpoints, edit them. Left-clicking on a breakpoint will allow you to edit it and set your own conditions for when to stop. For example, you can have the debugger stop if a specified variable has a certain value, or to stop only after the third or fourth time. You can also add actions and tell the debugger to automatically continue after evaluating the action.

Specific actions that you can select include debugger commands, log messages, and sounds. Sounds are one of my favorite things - if you want to check if certain code is called, you can add a sound breakpoint to hear feedback from your app or Xcode to know if that code is truly being called.

In addition to normal breakpoints, there are also project-wide breakpoints. One of the most helpful is the Exception Breakpoint. With this, you can choose to catch exceptions from Objective-C, C++, or both. Another project-wide breakpoint is the OpenGL ES Error Breakpoint, and a third is a Symbolic Breakpoint. This third option stops when you execute a specific function or method. The fourth breakpoint, Test Failure, has the debugger stop when test assertions fail.

To give an example, we can look at one particular symbolic breakpoint. Every time that a function is called on an object that does not recognize it, we can stop by inputting as a symbol [NSObject doesNotRecognizeSelector:]. You can add a symbolic breakpoint not just for functions that you know and have created, but also for private functions.

Compiler Errors (6:01)

With those techniques in our toolkit, we can catch problems more quickly. But what if we don’t get that far? What if we are stopped by a compiler complaint? Let’s look at one example of a compiler error and how to approach it.

func randomPointInRectangle(rectangle:CGRect) {
    return CGPoint(x: randomInt(rectangle.size.width), y: randomInt(rectangle.size.height))

    // compiler error for this line:
    // Cannot invoke 'init' with an argument list of type '(x:$T4, y: $T9)'
}

1. Remove Variables (6:47)

My first approach is usually to search Google (it’s a good first approach for many problems in life), but unfortunately, for this example there isn’t a great deal of helpful existing material. In this example, my function randomPointInRectangle returns a CGPoint created from randomInts. The first thing to try is to eliminate variables, so let’s replace the randomInts with integer values. After trying to return the CGPoint with values of 10 and 10, the error message has changed.

func randomPointInRectangle(rectangle:CGRect) {
    // return CGPoint(x: randomInt(rectangle.size.width), y: randomInt(rectangle.size.height))
    return CGPoint(x: 10, y: 10);

    // compiler error is now:
    // Cannot invoke 'init' with an argument list of type '(x: IntegerLiteralConvertible, y: IntegerLiteralConvertible)'
}

2. Split Instructions (7:29)

This error message is now slightly more helpful, because I know that I can, in fact, do what the error message tells me I cannot do — I know that CGPoint(x: 10, y: 10) is valid. What do we do now? It might seem like this is as far as we can go, but there is something more we can do: we can split our code. In the return statement, we are initializing a CGPoint and returning a value. When we split these two actions into two lines of code, we find that the error in the beginning was from the return value, not the initialization.

func randomPointInRectangle(rectangle:CGRect) {
    // return CGPoint(x: randomInt(rectangle.size.width), y: randomInt(rectangle.size.height))
    // return CGPoint(x: 10, y: 10);
    let point = CGPoint(x:10,y:10);
    return point;

    // compiler error is now:
    // 'CGPoint' is not convertible to '()'
}

The compiler now tells us that CGPoint is not convertible to an empty tuple, which is another way of saying Void. Taking a look at our function declaration, we see that we have neglected to declare the return type. Once that is changed, the original code compiles!

Launch Arguments (8:09)

Even with code that compiles, there might be bugs that aren’t immediately visible. For that, launch arguments are quite helpful. These are arguments that are passed to your project at start, and they override the user defaults. Launch arguments are added by going into the scheme editor and finding the tab called Arguments.

The first example of an argument is ConcurrencyDebug from Core Data. Working with Core Data causes you to work with ManagedObjectContext, where every object lives in its own context. If you want to do background work, then you will have two contexts, where objects from one context cannot be accessed from a different context. If you try to do this with launch argument turned on, you will quickly encounter Multithreading_Violation_AllThatIsLeftToUsIsHonor. This wonderfully-named error message lets you know that you have done something incorrectly.

The next example is SQLDebug. This argument shows you what happens under the hood. The “3” at the end of -com.apple.CoreData.SQLDebug 3 imparts the level of verbosity of output. Lower numbers provide less output, so the number 3 will provide you with as much output as possible.

There are many more launch arguments, and I recommend the article Tools For Core Data by Matthew Morey if you want to dive into more detail about how to look at your database.

Localization (9:59)

Launch arguments aren’t limited to Core Data, you can also use them if you want to check things such as app localization. To make sure that your UI is ready for short languages like English and long languages like German, NSDoubleLocalizedStrings will help you. This doubles every string in your application to see if your UI behaves correctly.

Another helpful tool is NSShowNonLocalizedStrings. Having this option turned on will give an alert if you have forgotten to add a translation to your localized file.

The AppleLanguages key allows you to specify multiple languages by code. If the translation for the first language isn’t found, the app falls back to the subsequently specified languages. Before I learnt about this one, I used to open settings and change the language to test the app, with the hope that I could remember how to change the language back again.

If you want to take a closer look at launch arguments, look into the Technical Note iOS Debugging Magic.

Debugging Tools (11:36)

My last topic is debugging tools, and I will mention a couple of tools that I’ve found very helpful.

Charles is especially useful if you frequently work with server communication. You can use this tool to see what kind of requests are being made, and view the JSON going back and forth between the server. This works not only for your own server calls, but also for any other server communication that is occurring. Charles also works for HTTPS requests. It can also be used to throttle your traffic, and record download statistics. Very handy!

The Swift REPL can be used for debugging, to test and interact with your app. When code breaks, typing repl into LLDB will allow you to interactively test code. You can call methods with different arguments, and test new functions by adding them to your existing code. To start with a clean slate, just type xcrun swift into your terminal.

Finally, “Activity Tracing” is a new feature of iOS 8. Suppose that your app passed all testing and debugging, but once released crash reports start to come in. If you add activity tracing, the crash report will show you those messages, and the user behavior that triggered the situation. Simply start an activity, add breadcrumbs, and you can trace messages. However, in order to prevent logging usernames or passwords, Apple prevents you from logging strings in trace messages. To learn more about how to use activity tracing, read the article Activity Tracing from objc.io.

Summary (14:37)

In this talk, we’ve learned about the expression command in LLDB. We also added breakpoints with sounds and actions, as well as different types including Symbolic and Exception Breakpoints. We learned how to tackle incomprehensible compiler errors, and how we can use launch arguments to actually see problems that we didn’t notice before. Finally, I introduced you to some very helpful debugging tools.

Thank you!


About the content

This talk was delivered live in March 2015 at Swift Summit London. The video was transcribed by Realm and is published here with the permission of the conference organizers.

Carola Nitz

Carola Nitz

4 design patterns for a RESTless mobile integration »

close