When developing an app in any language, getting diagnostic information about what’s happening during program execution is an absolute necessity. For iOS developers, Apple provides Instruments, an extremely robust set of tools integrated into Xcode for debugging code and finding performance bottlenecks. As good as Instruments is however, it can complicated to use and is often overkill for basic day-to-day debugging needs. In the first part of this article we’ll learn some basic diagnostic techniques that are extremely simple to set up and use, but not always obvious to the new iOS developer.
For demonstration purposes, we’ll create a simple sample project. Open Xcode and from the File menu, select New, and then New Project. A new workspace window will appear, and you will be presented with several application templates to choose from.
On the left-hand side, select Application from the iOS section. Select Single View Application and click the Next button. (Figure A)
- On the next pane, enter Diags for the Product Name and com.myappcompany as the Company Identifier. (Feel free to substitute your own product name and company identifier).
Leave the Class Prefix as is. It should be blank – XYZ is just a placeholder hint.
Make sure only Use Automatic Reference Counting and Use Storyboards are checked.
Set Device Family to iPhone, and click Next. (Figure B)
A new pane will appear asking you where you would like to save the project. XCode will create a Diags project folder inside the directory you select.
Once the project is created, Xcode will open a workspace window with your new project.
The NSLog Function
Click on ViewController.m in the Files & Groups pane and add the following statement to the viewDidLoad: method under [super viewDidLoad], then build and run the app. (Cmd-R is handy keyboard shortcut for “Build and Run”)
The NSLog function accepts a formatted string and logs it to the Apple System Log facility, commonly referred to as the “system log.” When running your app in Xcode on either the iPhone simulator or a real device, these messages can be found in two places. The first is the output window at the bottom of the main workspace as shown in Figure C. You may need to use the view control buttons referenced in the figure if the output window is not visible. As you can see the, our “hello world” message is displayed there along with a timestamp.
Before looking at the second location for log output, modify viewDidLoad: as follows and build and run the app again.
Looking again at Figure C, note the “Log Navigator” button has been highlighted in the upper left. Click on that button to bring up the Log Navigator. The output from the second run of app should already be displayed as shown in Figure D. Note that there is a separate “Build” and “Debug” entry for every build of our app. The Build entry shows messages generated during the compile and linking process, and is often a good place to troubleshoot missing external modules or the infamous and much-dreaded code signing errors. The “Debug” entry shows the diagnostics generated while our app is running.
This time we passed NSLog a formatted string containing a 32-bit signed integer format specifier “%d”, which was replaced by the integer expression “i + 1”. There are a variety of format specifiers and length modifiers available to cover all the data types you might want to pass. For example, using a format specifier of “%.2f”, we can output a floating point variable rounded to two decimal places. A complete list of format specifiers and modifiers can be found in the Apple documentation.
Basic use cases
NSLog statements can appear anywhere in your class methods, so you can use them as necessary to discover the values of instance variables or properties at various points during execution. Suppose you’ve got an instance variable that is getting set to an unexpected value, but you are unsure where the change is occurring. You might suspect you’ve got a method creating an unintended, but non-obvious side effect, and try something like this:
You may have to sprinkle these before/after logging statements at various places in your code until you can narrow down the location of the problem. As simple and trivial as this may seem, it is often the quickest way to determine where a problem is occurring when you are new to iOS development. You can certainly use Instruments to track variables, but the learning curve is steep, and it is best kept for more challenging problems as you become more experienced.
Tracing execution is another helpful use of NSLog. Change viewDidLoad: to look like this:
Now build and run the app and look at the output log.
The compiler provides two so-called “magic” variables __PRETTY_FUNCTION__ and __LINE__ that we can use to display the current class and method being executed and the line number in our source code of the NSLog statement. By adding this to each of your methods you can easily get a complete execution trace. As the complexity of your app grows, this becomes incredibly useful information and a huge time saver. (Figure E)
If we now combine these two ideas together, we can find out very quickly what the value of a variable is at any line in our source code. For example, if we create a method named someMethod containing the following code inside:
The result is shown in Figure F.
Now we have a powerful tool at our disposal, but despite its usefulness, it’s admittedly quite a lot to type and litters our source code, so in the second part of this series we’ll learn how to create a reusable compiler macro to greatly simplify it, and also see how to automatically remove these diagnostic statements from our code when building an app for distribution in the App Store.