Debugger Features in Xcode 4.5

Original author: Brian Moakley
  • Transfer
  • Tutorial
The only constant in software development is bugs. Let's face it, we never managed to do everything right the first time. Due to negligence or incorrect assumptions, software development becomes similar to making a pie in a motel teeming with cockroaches, except that in our case we create bugs ourselves. Fortunately, Xcode gives us many tools to keep insects horrified. Obviously, for this purpose there is a debugger that we know and love, but there is still much that it can do besides viewing variables and line-by-line debugging. This is a tutorial for beginners and advanced iOS developers, where you can get hands-on experience with some of the lesser-known but extremely useful debugging methods, such as:
- How to get rid of NSLog in favor of logging breakpoints;
- how to get rid of the TODO list in favor of generating compiler warnings;
- stop on conditions with expressions;
- Dynamic data change using LLDB and much more.
As you can see, the goal for me is to be a lazy developer. Luckily LLDB allows me to save my martini time. It provides me with great tools so that I don’t get stuck to my computer during the day or night. Sit back in your chair and discover your favorite drink. Time to get lazy!
I note that this tutorial assumes that you are already familiar with the basics of debugging in Xcode. If you are a beginner, I recommend going through this tutorial first .

So, let's begin


I have compiled a sample application for this project, which you can download here .
This app is called Gift Lister. It keeps track of the food you want your friends to buy. This app is similar to Gifts 2 HD, which recently received the Most Visually Impressive award . Gift Lister is similar to Gifts 2 HD but much, much worse.
For starters, Gift Lister is teeming with bugs. The developer (it was me in a different T-shirt) was very ambitious and tried to fix everything in the old-fashioned way. And yes, the application still does not work.
In this tutorial, you'll see methods on how to fix an application by acting as lazily as possible.
Opening the project, you will see various files. You can see that our application is a front-end to a model running CoreData.
Now that you’ve looked around, build and run the project. Not surprisingly, but the application crashes. Let's fix it.

Debugger console setup


First of all, you need to open the debugger console. You can open it by clicking on this button on the main panel:

Although this button is very nice, cute and comfortable, pressing it every time you debug it leads to unnecessary finger wear. Therefore, I prefer Xcode to do this for me.
To do this, open the Xcode settings by pressing ⌘, or go to the menu and select Xcode \ Preferences and click the Behaviors button.

Select the Starts item on the left side of the dialog box that appears. A group of options will appear on the right side, select the seventh checkbox and select Variables & Console in the last checkbox.
Do the same for the Pauses items .and Generates Output , which are located directly under Starts .
The Variables & Console option tells the debugger to show both the console and the list of local variables at the start of the session. If you want to display only the console, select Console View, if you want to see only a list of local variables, select Variable View.
The Current Views option launches the view that was in the last debugging session. For example, if you close the display of local variables and leave only the console, the next time you start the debugger, only the console is displayed.
Close the dialog box, then build and run the application.
Now, every time you launch your application, a debugger will appear, which will free you from the painful burden of pressing a button.

The NSLog Jam


Before proceeding, consider the definition of breakpoint.
Breakpoint is a point that allows you to stop a program at some point in time and perform some actions on a running program.
The program will stop at the designated point and allow you to evaluate your current state and allow you to carry out its implementation in steps.
You can also run code, change variables, and even make the computer quote Shakespeare. And you will do all this during this tutorial.
Ok, build and run the application. This is the first attempt to launch:

There is something to think about. You currently cannot see the source of compiler errors. In order to find the source, you need to put a breakpoint.
So, switch to the breakpoint navigator, as shown in the image below:

Then click on the plus at the bottom of the panel. From the menu that appears, select Add Exception Breakpoint .

Next, you should see a dialog box:

The Exception field provides the option to enable breakpoint for Objective-C, C ++ code, or for everything. Leave the default value (All).
The Break field allows you to stop execution when an error occurs or when catching an exception. Leave On Throw selected. If you intend to use an exception handler in your code, then you need the On Catch item. For our tutorial leave On Throw.
The last two fields we will consider during the training. Press buttonDone and then Build and run .
This time, the result has become much clearer.

Take a look at the console, now it is filled with logs, and many of them we do not need.
Logs are an important part of debugging an application. But we need to remove unnecessary messages so as not to clutter up the console. If you do not remove unnecessary messages, you will spend more time searching for the necessary messages, and, as a result, spend more time on the error than it deserves.
Open AppDelegate.m and you should see a lot of old messages in the didFinishLauncingWithOptions method . Select them all, delete.
Let's find the next log log. Open the search and findNSLog (@ "in viewDidLoad");

Click on FriendSelectionViewController.m in the search results and a line will open that displays messages in the log.
At the moment, the efforts that you spend on maintaining the logs are starting to accumulate. Perhaps it seems to you that you did not spend so much time, but the time spent tends to accumulate. By the end of the project, the time spent will be measured in hours.
Another disadvantage of hardcoding logs is that each time adding something new to your code base, you take the risk of new errors. All this takes a few keystrokes, a bit of autocompletion, a little distraction and, once, a bug appears in your running application.
Now is the time to get rid of the logs.
First, comment out our two lines that generate the logs. Then add breakpoints by clicking to the left of each expression.
Your code window should now look like this:

Click on the first breakpoint with the left mouse button while holding Control or with the right button and select Edit Breakpoint. In the dialog box, select Log Message from the Action menu. In the text box, type “in viewDidLoad”. The dialog should look like the image below:

Click the Done button, then launch the application. Now in the console you should see the message “in viewDidLoad”, but now it is generated not by NSLog, but with breakpoint!
But there is one big problem. The program stops, reaching a breakpoint while we do not want it. Changing this behavior is pretty easy.
Click on the breakpoint with the left mouse button while holding Control or the right button and select the “Automatically continue after evaluating” checkbox. Now run the application again.
This time, when the first breakpoint is reached, only a message is displayed, the program stops only at the second breakpoint.
Click on the second breakpoint with the left button while holding Control or the right mouse button. Select “Log Message” from the menu, then write “Loading Friends ...”. At the bottom of the dialog box, select the "Automatically continue after evaluating" checkbox. Click Done and launch the application.
The application works fine, until it crashes, but not all at once.
Believe it or not, you are still doing too much work. Click on the first breakpoint with the left mouse button while holding Control or the right button and replace "in viewDIdLoad" with% B. Launch the application again. The console will look like this:

Key% B displays the name of the method in which breakpoint was executed. You can also use% H to see how many times the method has been called. Simple expressions may also be included here.
So you can write:% B has been touch% H times. The console will display: -viewWillLoad has been touched 1 times.
Before you fix a critical bug, let's have some more fun. Click on the first breakpoint with the left mouse button while holding Control or with the right button and select Edit Breakpoint. In the dialog box, click the plus button. This button allows you to perform several actions on one breakpoint.
Select “Log Message” and enter “To be, or not to be”. Select the “Speak Message” option and press Done. The dialog should look like this:

Launch the application and enjoy the presentation.
Unfortunately, Log Messages does not have the flexibility of NSLog. To achieve it, we need to add some Debugger Actios.
For a demonstration, you will fix a critical bug. Launch the application and let the program crash. The stack will look like this:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+ entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name' Friend '
Something does not work in Core Data.
Checking the code you will see that NSManagedObjectContext is being pulled from the DataStore. Here you have a hunch that perhaps the DataStore is the source of the problem. DataStore is not part of CoreData. This is a hand-crafted singleton for encapsulating some core CoreData objects.
Add a breakpoint below the line: DataStore * dataStore = [DataStore sharedDataStore];
Click on the breakpoint with the left button while holding Control or with the right mouse button, select “Edit Breakpoint” and then select “Debugger Command”. In the text box, enter the following:
po dataStore
Check the box “Automatically continue after evaluating” and run.

As you expected, the value of dataStore is nil.
Open DataStore.m and you will see that sharedInstance always returns nil. Change the return value with
return nil;
on
return sharedInstance;
Launch the app. Hooray, it works (sort of)!


Expressions and breakpoints


The farther, the better, but as you can see the logs output using breakpoint do not show the time the message occurred, which can sometimes be useful for debugging the application. But the good news is, it’s easy to fix with breakpoint expressions!
Let's restore the logs in all their glory. Perform a right click or control click on the previous breakpoint in FriendSelectionViewController.m . Select "Edit Breakpoint." In the dialog box, change the command to
expr (void) NSLog (@ "dataStore:% @", dataStore)

The expr command will evaluate the expression in real time. The expression command must know the exact type of the return value, so type casting is necessary. Since NSLog does not have a return value, the return type must be void. Launch the app.
You should see something like the following:
2012-12-20 08: 57: 39.942 GiftLister [1984: 11603] dataStore:
The ability to output NSLog expressions through breakpoint will allow you to no longer stop the program just to display important data, and now you will not have the chance to introduce new bugs into the program simply because you do not touch the code, but the best thing is that You don’t have to frantically delete your debugging messages the night before release.
There is a slight difference between calling NSLog in the debugger and calling in code. Unlike code, the text "" will be displayed. This message is generated by LLDB, and unfortunately you cannot suppress it. The good news is that this behavior should fix in the next release of Xcode.
Let's warm off the application logging. This can be done simply by clicking the breakpoints button.

Click on it and then launch the application. Logs are no longer generated. You can also individually disable logging in the breakpoint navigator.
The days when you filled your code with log calls have finally passed!

Warnings, Errors, Returns, Good God


The application is working. The next thing we need to do is add friends so that you can make a list of preferences for choosing gifts. Launch the application, and when the application starts, click on the button that says “Add a friend”. The application will load another view controller in which there will be a field for entering text and selecting a date. Enter a name and select a friend’s birthday. Click OK.
You will return to the root view controller and a record of your friend will be added to the table. Click Add friend again.
Enter the name of another friend, but this time choose his birthday on February 31, 2010.
In the usual Date picker there is no such date, but not in the case of our application. In a fit of delirium, I decided to be ambitious and chose a regular picker instead of a date picker. Having done so, I had to rewrite the entire logic of checking dates, and, of course, this led to new errors.
click OK. Sorry, an invalid date was recorded. Time to debug to see what’s wrong.
Open AddFriendViewController.m and put breakpoint at the beginning of the method - (void) saveFriend.
In the simulator, click “Add a friend” and just like the last time, enter the wrong date. Follow the method step by step until you reach the line:
if ([self isValidDateComposedOfMonth:month day:day andYear:year]) {

Go into this method. Validation code missing. But this is not all, here is just a comment promising to improve the situation in the future.
Comments are a good way to describe the meaning of some pieces of code, but using them to manage tasks is useless. Even in small projects, there are too many different points in order to guarantee that not one of these comments will be simply forgotten.
The best way to not lose such comments is to make them really noticeable.
In the first line of the method - (void) isValidDateComposedOfMonth write the following code:
#warning add validation code

From now on, the project will report a new warning. Click on Issue Navigator and you will see there a new warning with your message.
If you are one of those developers who ignore warnings, try the following method
#error fix your code

From now on, a new error will appear in the project. And you can’t compile the application until you remove it. This was one way to keep track of your comments.
Delete both messages so that the application can compile.
In the first line of the method - (void) isValidDateComposedOfMonh, write the following code:
// TODO: Add validation code

Save the code and open the Jump bar. You should see something like this:

You can also write: FIXME :, ???:, and !!!:. ???: means "I have questions" while !!!: means "this is important."
These messages do not attract as much attention as warnings or errors, but they are more visible than the lone comment at the bottom of the method. It’s best to leave comments for, well, let's say for commenting and keep a list of tasks outside the code.
Let's now take a look at one small feature that has come to Xcode 4.4.
Launch applications leaving breakpoint in an empty validation method. Look at the list of variables in the debugger. Now do step out. Look at the list of variables again. You should see the following:

This feature has not received much attention, but it can make your life much easier. Note that the code was called from here:
if ([self isValidDateComposedOfMonth:month day:day andYear:year]) {

The code that calls the method now immediately uses the return value in the expression. Previously, if you wanted to check the return value, you would have to break the string and then output the value.
Now you can simply exit the method and look in the debugger for the return value.

Speaking your method to save data


At the moment, you already have enough data in the application. It is time to save them. In applications like this, saves should be so frequent as not to lose data. But this is not for our specific application. Our application saves data only when the user closes it.
Press the back button on the navigation bar, which will return you to the root controller, then simulate pressing the Home button. You can do this from the simulator menu by selecting Hardware \ Home or pressing shift-command-h.
Now stop the program from Xcode and run it again. The table is empty. The application failed to save anything.
Open AppDelegate.m . In the applicationDidEnterBackground methodYou should immediately see the problem. There is a method called doLotsOfWork . The work did not end on time, so iOS closes your application until it is finished cleaning. The result of this early termination is that the SaveData method is not called.
Let's first make sure that the data is saved. In applicationDidEnterBackground, move the call to [[DataStore sharedDataStore] saveData]; before calling doLotsOfWork as follows:
[[DataStore sharedDataStore] saveData];
[self doLotsOfWork];

Now add a breakpoint to the line doLotsOfWork. Next, right-click or control-click on breakpoint and select Edit Breakpoint. Select Sound Action and set the sound to Submarine. When I deal with sounds, I try not to use system sounds since they are easily overlooked.
Next, click on the Automatically continue after evaluating checkbox and finally launch the application.

When the application starts again, add a new user and click the Home button. Immediately after closing the application, you should hear a submarine sound signaling that the data has been saved.
Stop the application from Xcode and click the Run button. You will see your data in all its glory.
Using sounds is a good way to learn about the execution of a certain part of the code without viewing the logs. You can also use your sounds if, for example, you want to hear the sound of an explosion in the event of an application crash.
To do this, just put your sounds in the folder: YOUR_HOME_DIRECTORY / Library / Sounds but you will have to restart Xcode so that it can see them.

Conditions for successful debugging


In development, there are times when it is necessary to change the state of a program at certain intervals. Sometimes these changes occur in a huge sequence of events that make normal debugging impossible. And then the conditions come into play.
Now you have several friends saved in the application, click on one of the names to open the gift interface. This is just a simple grouped table that can be sorted by whether a gift can be bought or not.
Click the Add button on the navigation bar to add a new item. For the name, select Shoes. For the price of 88.00. Click OK. These shoes should appear in the gift table.
Now add the following things:
Sleigh / 540.00
Candles / 1.99
XBox / 299.99
iPad / 499.99
Oops You just realized that you really wanted to record a PS3 instead of an XBox. You can simply click on a cell for editing, but for demonstration purposes, you will do this through the debugger.
Open GiftListsViewController.m and find the cellForRowAtIndexPath method. Add a breakpoint on the line under the code if (gift) {

Now right click or control click on breakpoint and select “Edit Breakpoint”.
It is time for our condition. Treating him is as easy as treating messages. Add the following code:
(BOOL) [gift.name isEqualToString:@"XBox"]


LLDB requires us to typecast, so we put BOOL before the expression. Click Finish. Now click the Bought button. New data is loaded into the table but breakpoint does not work. Click the save button. This time everything will stop the selected item in the debugger console will highlight.
Add the following to the debugger console:
(lldb) expr (void) [gift setName:@"PS3"]

Now click the Play button and the table will continue to load and the PS3 will replace the XBox in the gift list.
The same result can be achieved by setting the number of iterations. Control click or right click on breakpoint and select 'Delete Breakpoint'. Xcode can be slightly unstable when conditions change, so it's best to start all over from scratch. Add a new breakpoint in the same place. This time, ignore the text box and select number 3. Press Done.

Then click Bought and Saved.
We have to get to the same breakpoint. To make sure that we are on the right site, write:
(lldb) po gift

Now return the object to its previous state:
(lldb) (void)[gift setName:@"XBox 360"]

The table should reflect the changes. Isn't real-time editing great?

Demolition setting


When developing data management applications, it is often important to clean up data warehouses. There are several ways to do this, it restarts the iPhone simulator before finding the actual storage on your computer and deleting it. Doing it over and over can be a bit tedious, so you can be a little lazy again and let Xcode do it for us.
We will start by creating a script. A script is a set of commands that automates some actions of an operating system. To create a new script, select New file in the application menu. Click File \ New \ File or command-n. Select the Other category and there select the Shell script.

Enter the name wipe-db.sh

Now we need to find a real data store.
Open your terminal. If you do not know where the terminal is located, you can find it in the folder of your application in the Utilities folder.
After starting the terminal, go to your home directory by typing
 YourComputer$ cd ~

Then list the files and folders in your directory by typing
 YourComputer$ ls

Browse your directory, if you do not see the Library folder, enter the command
YourComputer$ chflags nohidden ~/Library/

Then restart the terminal.
Now move to the folder with the iPhone simulator using the command:
 YourComputer$ cd ~/Library/Application\ Support/iPhone\ Simulator/6.0/Applications

List the directory
YourComputer$ ls

You will see many different directories, their number depends on how many applications are installed on the simulator. You will need a trial and error nati folder with GiftLister. To go to the folder, type: cd THE_NAME_OF_YOUR_FOLDER
To save time, enter only the first three letters of the folder name and press Tab. The terminal will append the folder names for you. If not, keep typing until it starts autocomplete. In my case, this is the folder 0B1E5AD3-7292-45A6-BB5D-F1C004AC47F9 for this I will enter
YourComputer$ cd 0B1

and press Tab.
Inside you should see the GiftLister.app file , if you do not find it, try a different folder. This project is also running on the iOS6 simulator. If you are using an earlier version of the simulator, enter
YourComputer$ cd ~/Library/Application\ Support/iPhone\ Simulator/

Then enter ls to display its contents. Select the correct version of the simulator, and go to the directory with:
cd VERSION_NUMBER/Applications

eg
cd 6.0/Applications

When you find your application folder, enter
YourComputer$ cd Library

and enter ls.
You should see the giftlister.sqlite file. Jackpot.
Now print the path to the file with the pwd command.
Copy the path and paste it into the script adding at the end of /giftlister.sqlite
your path should look something like this:
/Users/Brian/Library/Application Support/iPhone Simulator/6.0/Applications/0B1E5AD3-7292-45A6-BB5D-F1C004AC47F9/Library/giftlister.sqlite

Unfortunately you cannot use spaces, so you have to transform
/iPhone Simulator/
/Application Support/

in
/iPhone\ Simulator/
/Application\ Support/

the full path will look like
/Users/Brian/Library/Application\ Support/iPhone\ Simulator/6.0/Applications/0B1E5AD3-7292-45A6-BB5D-F1C004AC47F9/Library/giftlister.sqlite

Next, add a delete command that looks like just rm.
Your script will look like this:

Save and close your script.
By default, scripts are created for reading only, you will need to make this script available for execution. Return to your home directory by typing
YourComputer$ cd ~

Then do ls.
Go to your project folder. If you saved it on your desktop, you can go there simply by doing
YourComputer$ cd Desktop
 YourComputer$ cd GiftLister

To go up to the folder, enter cd ...
After a long walk through the terminal, you should see the project folder. To execute the script, simply enter
 YourComputer$ chmod a+x wipe-db.sh

Chmode is a program for changing file permissions. a + x allows the file to be executable for all users, groups, and others.
Wow ... how many things. Take a break. You deserve it. Sometimes in order to be lazy you have to work hard.
Close the terminal and return to Xcode. Open AppDelegate.m . Set breakpoint on the first line of the didFinishLaunchingWithOptions method. Right or control click on breakpoint and select “Edit Breakpoint”. Add Action and select Shell Command. In the next dialog box, click the Choose button and select the script you just created. Click on the “Automatically continue after evaluating” checkbox and then click Done. Stop the simulator if it is running. Now run the application. The database will be deleted.
The simulator tends to cache large amounts of data, so I think it's best to click Clean from Xcode on the Clean menu, and then click Build and run.
All this required a bit of work when setting up, but now the database can be cleaned up with just the click of a button. When this behavior is undesirable, just disable breakpoint.

The author of this post is Brian Moakley , a person who is not only an iOS app developer and fiction writer, but also the first full-time Razerware employee.

PS Write about all grammar and syntax errors in PM, they will be fixed as quickly as possible.

Also popular now: