📜 ⬆️ ⬇️

Debugger features in Xcode 4.5

The only constant in the development of software are bugs. Let's face it, we never managed to get it right the first time. Due to negligence or incorrect assumptions, software development becomes similar to cooking a cake in a motel full of cockroaches, except that in our case we create bugs. Fortunately, Xcode gives us a lot of tools to keep insects in horror. It is obvious that for this purpose there is a debugger that we know and love, but there is still much that he can do besides watching 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 less well-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 modification using LLDB and more.
As you can see, the goal for me is to be a lazy developer. Luckily, LLDB allows me to save my time in a martini. It provides me with excellent tools so that I will not be glued to my computer during the day and night. Sit back in your chair and open your favorite drink. Time to become lazy!
Note that this tutorial implies that you are already familiar with the basics of debugging in Xcode. If you are a beginner, I recommend to go 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 tracks the postings you want to buy to your friends. This application is similar to the Gifts 2 HD, which recently won the Most Visually Impressive award. Gift Lister is similar to Gifts 2 HD but much, much worse.
For starters, Gift Lister is just teeming with bugs. The developer (it was me in another T-shirt) was very ambitious and tried to fix everything in an old-fashioned way. And yes, the application is still not working.
In this tutorial, you will see methods for how to fix the application by acting as lazily as possible.
Having opened the project, you will see various files. You can see that our application is a front-end to the model running CoreData.
Now that you've looked around, build and run the project. Not surprisingly, the application crashes. Let's fix it.

Configuring debug console


The first thing 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 leads to unnecessary wear on your fingers. That's why I prefer Xcode to do it 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 and Generates Output points that are directly below the Starts item.
The Variables & Console option tells the debugger to show both the console and the list of local variables when starting the session. If you want to show only the console, select Console View, if you want to see only the 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 closed the display of local variables and left only the console, the next time you start the debugger, only the console will be displayed.
Close the dialog box, then build and run the application.
Now every time you start your application, a debugger will appear, which will free you from the agonizing burden of pressing a button.

The NSLog Jam


Before we continue, let's look at 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 assess your current status and allow you to perform your execution step by step.
You can also run code, change variables, and even force a computer to quote Shakespeare. And you will do all this during this tutorial.
Well, build and run the application. This is the first launch attempt:

There is something to think about. Currently, you cannot see the source of compiler errors. In order to find the source, you need to set a breakpoint.
So, switch to breakpoint navigator, as shown in the picture 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 an option to enable breakpoint for Objective-C, C ++ code or for everything. Leave the default value (All).
The Break field allows you to stop the execution when an error occurs or when catching an exception. Leave On Throw selected. If you are going to use an exception handler in your code, then you need the On Catch option. For our tutorial, leave On Throw.
The last two fields we consider in the course of training. Click Done 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 we don’t need many of them.
Logs are an important part of debugging an application. But we need to remove unnecessary messages in order not to clutter up the console. If you do not remove unnecessary messages, you will spend more time searching for the right messages, and, as a result, spend more time on the error than it deserves.
Open AppDelegate.m and you will need to see a lot of old messages in the didFinishLauncingWithOptions method. Select them all remove.
Let's find the next log of logs. Open the search and find NSLog (@ "in viewDidLoad");

In the search results, click on FriendSelectionViewController.m and then a line opens that displays messages to the log.
At the moment, the efforts that you put into logging are beginning to build up. Perhaps it seems to you that you have spent not 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 you add something new to your codebase, you take the risk of new errors. All this takes a few keystrokes, a little autofill, a little distraction, and one day, a bug appears in your running application.
Now it is time to get rid of the logs.
First, comment out our two lines that generate 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 with the Control held down 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 box should look like this:

Click the Done button, then launch the application. Now in the console you should see the messages “in viewDidLoad”, but now it is not generated by NSLog, but by using a breakpoint!
But there is one big problem. The program stops reaching a breakpoint while we do not want it. It is easy to change this behavior.
Click on the breakpoint with the left mouse button while holding Control or with the right button and select the check box "Automatically continue after evaluating". 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 with the right mouse button. Select "Log Message" in the menu, then write "Loading Friends ...". At the bottom of the dialog box, select the "Automatically continue after evaluating" checkbox. Click Done and run the application.
The application works fine, as long as it does not fall, but not all at once.
Believe it or not, you still do too much work. Click on the first breakpoint with the left mouse button while holding Control or with the right button and replace “in viewDIdLoad” with% B. Run the application again. The console will look like this:

The key% B displays the name of the method in which the breakpoint was executed. You can also use% H to see how many times the method has been called. Also simple expressions can 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 with the Control held down 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 box should look like this:

Launch the app and enjoy the show.
Unfortunately, Log Messages does not have the flexibility of NSLog. To achieve it, we need to add some Debugger Actios.
To demonstrate, you will fix a critical bug. Run the application and let the program crash. The stack will look like this:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: entity name 'Friend'
Something does not work in Core Data.
By checking the code, you will see that NSManagedObjectContext is pulled from the DataStore object. Here you have a premonition that, perhaps, the DataStore is the source of the problem. DataStore is not part of CoreData. This is a manually created singleton to encapsulate 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 the right mouse button, select “Edit Breakpoint” and then select “Debugger Command”. In the text box, type the following:
po dataStore
Check the “Automatically continue after evaluating” checkbox and run.

As you expected, the dataStore value is nil.
Open DataStore.m and you will see that sharedInstance always returns nil. Change the return value with
return nil;
on
return sharedInstance;
Run the application. Hooray, it works (like as)!

')

Expressions and breakpoints


The further, 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 an application. But there is good news, 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 exactly the type of the return value, so type casting is necessary. Since the NSLog does not have a return value, the type of the return value must be void. Run the application.
You should see something like this:
2012-12-20 08: 57: 39.942 GiftLister [1984: 11603] dataStore: <DataStore: 0x74c3170>
The ability to output NSLog expressions through breakpoint will allow you to no longer stop the program only to display important data, and now you will not have a chance to add new bugs to the program simply because you don’t touch the code, but the best is that you do not have to feverishly delete your debug messages the night before the release.
There is a slight difference between calling NSLog in the debugger and calling in the code. Unlike the 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 be fixed in the next release of Xcode.
Let's turn off logging the application. This can be done simply by pressing 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 calls to the logs are finally over!

Warnings, errors, return values, dear


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. Start 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 text box and a date selection. Enter a name and select a friend's birthday. Click OK.
You will be returned to the root view controller and an entry about 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 all the logic for checking dates, and, of course, this led to the emergence of new errors.
Click OK. Unfortunately, the wrong date was recorded. Time to debug to understand what is wrong.
Open AddFriendViewController.m and place the breakpoint at the beginning of the method - (void) saveFriend.
In the simulator, click “Add a friend” and, just like last time, enter the wrong date. Perform the method step by step until you reach the line:
if ([self isValidDateComposedOfMonth:month day:day andYear:year]) { 

Go to this method. There is no verification code. But that's not all, here is just a comment promising to correct the situation in the future.
Comments are a good way to describe the meanings of some pieces of code, but using them to manage tasks is useless. Even in small projects there are too many different points to ensure that not one of these comments will not 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 this point on, the project will report a new alert. Click on Issue Navigator and you will see a new warning there with your message.
If you're one of those developers that ignore warnings, try the following method.
 #error fix your code 

From this point on, a new error will appear in the project. And you will not be able to compile the application until you fix it. It was one of the ways 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 noticeable than the lone comment at the bottom of the method. It is best to leave comments for, well, let's say for commenting and keep a list of tasks outside the code.
Let's now consider one small feature that appeared in Xcode 4.4.
Start applications leaving breakpoint in an empty validation method. Look at the list of variables in the debugger. Now 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 need to split the string and then output the value.
Now you can simply exit the method and see the return value in the debugger.

Sound 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, saving should be so frequent as to not lose data. But this is not for our particular application. Our application saves data only when the user closes it.
Press the back button on the navigation bar, which will take you back 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 could not save anything.
Open AppDelegate.m . In the applicationDidEnterBackground method, you should immediately see the problem. There is a method called doLotsOfWork . Work did not end on time, so iOS closes your app before it completes its cleanup. The result of this early termination is that the SaveData method is not called.
Let's first make sure the data is saved. In the applicationDidEnterBackground, move the call to [[DataStore sharedDataStore] saveData]; before calling doLotsOfWork as follows:
 [[DataStore sharedDataStore] saveData]; [self doLotsOfWork]; 

Now add a breakpoint to the doLotsOfWork line. Next, make a right or control click on the 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 as they are easily overlooked.
Next, click on the Automatically continue after evaluating checkbox and finally launch the application.

When the application starts up again, add a new user and click the Home button. Immediately after closing the application, you will need to hear the sound of the submarine, indicating 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 executing a certain part of the code without viewing the logs. You can also use your own sounds if, for example, you want to hear the sound of an explosion when the application is dropped.
To do this, simply place your sounds in the folder: YOUR_HOME_DIRECTORY / Library / Sounds but you will have to restart Xcode so that it can see them.

Successful debugging conditions


There are moments in development when it is necessary to change the state of a program at regular intervals. Sometimes these changes occur in a huge sequence of events that make normal debugging impossible. And here the conditions come into play.
Now that you have several friends saved in the app, click on one of the names to open the interface for gifts. This is just a simple grouped table, which can be sorted according to 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 table of gifts.
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 burn a PS3 instead of an XBox. You can simply click on the cell to edit it, but for the sake of demonstration, you will do this through a debugger.
Open GiftListsViewController.m and find the cellForRowAtIndexPath method. Add breakpoint to the line below the code if (gift) {

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


LLDB requires us to cast types, 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 save. This time everything stops the selected item in the debugger console is highlighted.
Add the following to the debugger console:
 (lldb) expr (void) [gift setName:@"PS3"] 

Now press 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 may be slightly unstable when conditions change, so it’s better to start from scratch. Add a new breakpoint in the same place. This time, ignore the text field and select the number 3. Press Done.

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

Now let's return the object to the previous one:
 (lldb) (void)[gift setName:@"XBox 360"] 

The table should reflect the changes. Well, isn't real-time editing great?

Setup for demolition


When developing data management applications, it is often important to clear the data stores. There are several ways to do this, it restarts the iPhone simulator before finding the actual storage on your computer and deleting it. Doing this again and again 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 certain actions of the operating system. To create a new script, select New file in the application menu. Click File \ New \ File or command-n. Select the category Other and select Shell script there.

Enter the name wipe-db.sh

Now we need to find a real data repository.
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 

View your catalog 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 method on the folder with GiftLister. To go to the folder, enter: 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 build the folder names for you. If not, continue typing letters until it starts to autocomplete. In my case, this is folder 0B1E5AD3-7292-45A6-BB5D-F1C004AC47F9 so I’ll enter
 YourComputer$ cd 0B1 

and press Tab.
Inside you should see the GiftLister.app file, if you don’t find it, try another folder. Also, this project is performed on iOS6 simulator. If you are using an earlier version of the simulator, enter
 YourComputer$ cd ~/Library/Application\ Support/iPhone\ Simulator/ 

Then type 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 file giftlister.sqlite. Jackpot
Now output the path to the file with the command pwd.
Copy the path and paste it into the script by adding /giftlister.sqlite at the end
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 can not use spaces, for this you have to transform
 /iPhone Simulator/ /Application Support/ 

at
 /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 just like rm.
Your script will look like this:

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

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

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

Chmode is a program for changing file permissions. a + x allows a file to be executable for all users, groups and others.
Wow ... so many things. Take a break. You deserve it. Sometimes in order to be lazy you have to work well.
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 an Action and select Shell Command. In the next dialog box, click the Choose button and select the script you just created. Click 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 has a tendency to cache large amounts of data, so I think it's best to click Clean from Xcode in 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 cleared with just one click of a button. When this behavior is undesirable just turn off the breakpoint.

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

PS For all grammatical and syntax errors, write to the PM, they will be corrected as quickly as possible.

Source: https://habr.com/ru/post/169353/


All Articles