Developers, rejoice. We have an excellent debug assistant to add to your toolkit. It’s name is Proxyman – from Proxy + middleMan. Natively built for macOS, the app is an easy way to intercept HTTP/HTTPS requests and debug from your Mac, iOS simulator, or remote devices without disrupting your network connection.
Three easy panels, no digging around
Adds in options to delete cookies, reset the app, hide pre-release software, and more. How to Enable the Debug Menu In a Bunch of Apple's Mac Apps. Thorin Klosowski.
Everything is done inside the Proxyman window, separated into three areas. In the left panel, you’ll see the list of all apps and domains – the Source List. The flows from selected sources will be reflected in the Flow List in the middle. The right panel, Flow Content, is where you can view all the Request and Response content.
As soon as you launch the app, it automatically overrides your proxy config in the Network Preferences. So you’re all set, right from the start.
Organize sources to view HTTPS responses
Even with hundreds of domains suffocating your Source List, there’s an easy way to search through. Use Ctrl+Shift+F to find an app or domain, pin, and move via drag and drop – it’s just like Finder, no complicated commands. Once you’ve done all the organizing, click on the domain or app, select URLs, and navigate to the right panel to view HTTPS responses.
Activate SSL proxying
While HTTPS responses are encrypted, you’ll have to enable SSL proxying for one domain or related domains first. To simplify the process, Proxyman has the option of automatic SSL Certificate activation. For seasoned developers, adding CA Certificate to Keychain manually won’t be a problem. You can also choose to install Certificate from simulator or iPhone.
Superb debugging tools
Now it’s time to debug. Proxyman will automatically capture HTTPS requests and responses, which you can view in JSON. If you prefer, you can use your favorite editor like Atom, Code, or TextMate. Without leaving the app, navigate between Header, Query, Cookies, and JSON response as well as debug with different params through Edit & Repeat. If you want to share your session, use the Export option.
Featured on Product Hunt and Github, Proxyman has been a part of developers’ tribe for a while now. We’re pleased to say it’s also a part of the Setapp package, along with 150+ useful apps for Mac.
Meantime, prepare for all the awesome things you can do with Setapp.
Read onSign Up
Finding and fixing bugs in your app is exciting, isn’t it? … No!? You probably don’t enjoy debugging in Xcode, but it doesn’t need to be a drag. In this article, you’ll learn a few expert techniques that make debugging your iOS apps a breeze.
Here’s what we’ll get into:
How To Debug Ios App In Mac
- How you can use Xcode’s debugging tools
- Poor man’s debugging with
print()
- Breakpoints, exceptions and steppers
- Compilation vs. runtime errors
- A helpful workflow to find and fix bugs
Making mistakes is part of the learning process, especially if you’re learning how to build iOS apps. Finding and fixing bugs can be frustrating if you don’t know where to look. The debugging tools you find in Xcode will help you find the root cause of a bug quickly and save you from a lot of frustration.
Ready? Let’s go.
Get Started with Debugging in Xcode
Let’s dive in with the easiest debugging technique ever: poor man’s debugging. You can do this with the print()
function, like this:
In the above code, we’re saving a hypothetical Tweet
object. On the last line, we’re printing a bit of text to the Console. When the line of code is executed, it’ll print the text output. Printing things like this can help you figure out what’s going on in your code.
Debugging with print()
is so simple, but so powerful. Some iOS developers will say you should use actual debugging tools, like breakpoints, but they don’t know how helpful code peppered with some print()
‘s can be for your learning experience. You can see what’s going on, with print()
.
Quick Tip: You can use literal expressions to print function names, line numbers and filenames with #function
, #line
and #file
. That way you know exactly where print output comes from. Like this: print('(#file):(#line), (#function) --- FUSRODAH!')
or print(#function)
. Neat!
Learn how to build iOS apps
Get started with iOS 14 and Swift 5
Sign up for my iOS development course, and learn how to build great iOS 14 apps with Swift 5 and Xcode 12.
Compilation vs. Runtime Errors
Before we continue, let’s discuss two types of errors (and bugs).
- Compilation Errors: These errors and bugs show up when your app is compiled (or “built”). For example, you’ve made a typo and the Swift compiler chokes on it.
- Runtime Errors: These errors and bugs happen when your app is running on an iPhone. The way your app works leads to an error or exception, and your app crashes.
Compilation errors typically show up in the Xcode editors, and usually in the Console. Runtime errors always show up in the Console, and occasionally in the Xcode editor.
In both cases, you’ll see a message that describes the error. When your app runs on an actual iPhone, and you’ve connected the iPhone to your Mac, you’ll be able to see the error messages the app spits out. And in many cases, Xcode will even point you to the line the error happened on!
It’s important to note here that an error message may not directly point to the root cause of an error. It could happen, for example, that your app crashes due to an error somewhere in the code. The actual cause of the crash is at some other place in your code.
A few common bugs in Swift are:
- Off-by-one error (or Index out of bounds)
In the above screenshot, for example, the error message is Index out of range. The highlighted line isn’t to blame here, but the code a few lines above it. We’ve accidentally reported the wrong size of the places
array – and that’s causing bugs further down.
How To Find the Root Cause of a Bug
Imagine you’ve just coded a feature in your app, and when you’re about to finish implementing the feature – your app crashes. Oops! There’s a bug somewhere! Now what?
Fixing bugs, as frustrating as it may sometimes be, can be very exciting. It’s like being a detective, but with code. You get to trace the bug back to its roots, and then solve it. When you look at it this way, finding and squashing bugs can be pretty fulfilling!
Think about how much you can learn about iOS development while solving a bug. You can become a 10x better coder by just solving bugs…
In general, there are 3 types of bugs:
- Bugs that have a clear error message, like “Index out of bounds”
- Bugs that don’t have a clear error message, like EXC_BAD_ACCESS
- Bugs that don’t have an error message at all (yikes!)
When you’re debugging in Xcode, you’ll always want to work toward finding an error message. You can’t fix a bug if you don’t have a clear error message. If possible, you also want to be able to reproduce the bug when needed. That way you can test if you actually solved the bug, later on.
Finding the error message of a bug is important, and so is being able to reproduce the bug! Focus your debugging efforts here.
A Great Workflow for Fixing Bugs
Here’s a recommended workflow for fixing bugs:
- Your app crashes. There’s a bug!
- You attempt to find the error message. Error messages are often shown in the Xcode Console, but you may have to dig a little deeper.
- You read and “decode” the error message. What does it mean?
- You come up with a solution to fix the bug and try it out. Didn’t work? Go back to step 2.
This makes bug fixing sound really simple, right? Obviously, a lot of work can go between step 3 and 4. The bug fixing process is simple, but not easy. (There’s a difference!)
In many cases, especially if you’re a beginner coder, you can Google the error message you found. This usually brings you to Stack Overflow, a blog, Reddit or Quora. In most cases Stack Overflow, or a related blog, has the answer to the error you’re seeing.
Based on what you found, you implement the solution in your own app. Even though most Stack Overflow answers explain what the cause of the bug is, you still need to do some custom coding to implement the solution.
When a ready-made bugfix can’t be found via Google, how do you debug your iOS app project? It’s also smart to not only rely on others to fix bugs for you. You can learn a ton about iOS development, just from debugging issues.
Stuck with a bug? It helps to explain the problem to someone else. I call this “Solve by House”, named after the problem-solving technique the fictional Dr. House uses in the TV series House M.D. Programmers often call this rubber duck debugging. By explaining the problem to someone else – or a rubber ducky – you carefully step through the problem, and this usually gives you an insight in a solution. Give it a try!
Debugging with Breakpoints in Xcode
A great approach to finding and fixing bugs in Xcode is by using breakpoints. With a breakpoint, the Xcode debugger can stop your code at almost any moment and show you the exact state of your app at that point. You can do that by adding a breakpoint to a line of code in your app.
When the app executes, and then “hits” that line of code, execution of your code comes to a halt, and you can look inside the guts of your app. You can inspect the exact values of variables, properties etc. at that point. And from there, you can step line-by-line through your code.
In the above image, you can see:
- The stacktrace. You can think of the stacktrace as a “function history” or “breadcrumb”. With it you can see the functions that were called before the breakpoint was reached. For instance, in the screenshot you can see the current function,
viewDidLoad()
. - The breakpoint. You can set a breakpoint by clicking on the gutter, right before a line. A blue arrow should appear. You can deactivate the breakpoint by clicking it again, and remove it by right-clicking.
- The breakpoint actions. You can take several actions when a breakpoint is hit, like continuing execution. More on these actions, later.
- The app values and state. You can inspect the values at the current point in the app’s execution. In the example, you can clearly see the contents of the
names
array.
When you look at the bug in the previous example, where you tried to access item with index no. 8 in an array with only 5 items, you see that using a breakpoint you can inspect the size of names
. This has the same effect as print
– although breakpoints are more advanced.
Quick Tip: A breakpoint will halt your code before the highlighted line is executed. It’s halted on that line. Good to know!
Let’s take a look at the tools you can use with breakpoints:
From left to right, you’ve got several options:
- Hide the debug area. Pretty self-explanatory!
- Deactivate breakpoints. This button deactivates all breakpoints for the current session.
- Continue program execution. This will continue execution of your code, until the debugger hits the next breakpoint. Keep in mind that this can be the same breakpoint, for instance when your code repeats or reaches the same point again.
- Step over. This continues the code, but only to the next line.
- Step into. This continues the code, by stepping into the current line. You go one level deeper, for instance by going into another function.
- Step out. This continues the code, by stepping out of the current function. You go one level higher.
Let’s discuss those step over-into-and-out tools more in-depth.
Breakpoint Steppers: Step Through Your Code
With a stepper, you can step through the lines of code of your app. You can literally start at one line, then continue to the next, and the next, and so on. You execute the code line by line. You can also step into (and out of) function calls, to follow the “red thread” into a function.
Think of your code as a tree-like structure. You start at one point, then go into a function, and when the function finishes, you go out of the function again. It’s like how a caterpillar would eat through a cake with different levels.
Debug Android App On Device
When you’re going over your code line-by-line with breakpoints, you can decide to “step over” a function, or go “into” that function. When you go in, you can go out again too.
Practicing stepping through lines of code is easiest when you code a simple loop. You can see in the image below that there’s a for-loop that calculates the sum of an array of numbers. The code breaks on the line inside the for-loop.
Make sure that you understand this, about the example above:
- The loop has ran for 5 times, from
n
= 0 to 4 - At this point,
n = 5
butnumbers[5]
hasn’t been added tosum
yet - The variable
sum
is equal to1 + 3 + 7 + 9 + 14
, the sum ofnumbers[n]
from 0 to 4
Stepping into a function isn’t always possible, or helpful, because you can only go over your own code step-by-step. You can’t inspect the source code of UIKit frameworks, so when you try to go in, you’ll only see assembly code. (You can debug this, but that’s for another blog post…)
How does this help you solve bugs? You can inspect the values your code has. In many cases, bugs occur because your app gets to a state that you hadn’t expected. Inspecting your code often gives you insight into the state of your app, and helps you to solve bugs.
Quick Tip: When a line with a breakpoint is reached, that line is not yet executed. The breakpoint sits “before” that line. You can clearly see that in the above screenshots. The app crashes on the line that has the breakpoint, but you can see in the debug output that the app hasn’t crashed yet (no error message). So, when you step over line 21, i.e. execute that line, the app crashes.
Working with Exception Breakpoints in Xcode
In some cases you can’t set a breakpoint in your code, because a bug occurs in a framework or Cocoa Touch library that you don’t control. When you can’t reach those lines of code, you can’t set a breakpoint.
How do you debug that? That’s where exceptions come in. You might know them from Swift error handling with do-try-catch
. Some functions can “throw” exceptions when an error occurs.
You’re supposed to catch those errors with a do-catch
block, but in many cases you deliberately want to crash the app so you can solve the bug that causes the exception.
A fairly typical exception is the NSInternalInconsistencyException
. This is a general error exception that’s thrown in a scenario where your code gets to a state it can’t support.
When such an exception is raised, Xcode won’t always take you to the line that caused the exception. You can still find it, like this:
Instead of setting a breakpoint on a specific line, you set a breakpoint for any exception that’s thrown in your app code. When an exception is thrown, Xcode will take you to the line of code that caused it. This often helps you to pinpoint the line of code that causes the error.
You can set an exception breakpoint by going to the Breakpoint navigator, on the left of Xcode, and then clicking on the small +
-button at the bottom-left. Then, choose Exception Breakpoint. You can also add several other breakpoints, for instance for the Swift Error type. That’s it!
Learn how to build iOS apps
Get started with iOS 14 and Swift 5
Sign up for my iOS development course, and learn how to build great iOS 14 apps with Swift 5 and Xcode 12.
Further Reading
Solving bugs is exciting! You get a chance to dive into your app’s code, figure out why it isn’t working, while learning about development. Debugging can also be frustrating…
Here’s what we’ve discussed:
- A helpful workflow to find and fix bugs
- How you can use Xcode’s debugging tools
- Breakpoints, exceptions and steppers
- Compilation vs. runtime errors
- Poor man’s debugging with
print()
Want to learn more? Check out these resources: