The Three Debugging Tools that Solve 99% of Problems in iOS

Nigel Faustino
Level Up Coding
Published in
7 min readMay 29, 2020

--

One of the top things I learned as an iOS engineering fellow at Formation was how to debug, and it’s a skill I really don’t see enough people talking about. Articles and videos online often go over more arcane debugging techniques because that’s what’s interesting, but in my experience, these 3 very basic tools are what actually solve 99% of bugs. It’s better to master the fundamentals than jumping to a niche solution you’ll forget in an hour.

You probably know most, if not all, of these tips. That’s great! These tips aren’t here because you’ve never heard of them before; they’re here because you do know them but might not use them enough. Think of this as more of a reminder of what’s inside your toolbox. I’m going to go over the tips and then show an actual example of how I used them to solve a bug in our product.

Search

Scrolling is a waste of time. Optimizing search is all about ways to find info faster than aimlessly scrolling around. A lot of great engineering is all about doing 10x the amount of work in half the amount of time, even for things as basic as this. Let’s go over a few of the ways we can search better:

Command + Shift + F. Keyboard shortcuts.

Matching Case whenever possible. Use standard Swift naming conventions to help you find things faster (lowercase if you’re searching for instances instead of classes).

Limit your scope. 99.9% of time, you don’t care about search results in your third party libraries. Limit your search to just your project, or maybe even to a directory, like this:

Use patterns. If you’re not sure about the exact naming of something, you could use Regex in your search, but I personally hate reading Regex. Luckily, XCode lets us insert patterns instead, which are much easier to use. Here’s a search for force unwrapping:

Filter your results. You might want to find search results only in your view controller files. (Good naming conventions help here as always.) Filter your search results by searching for “VC.swift”, like this:

Use the related items menu. Once you’ve actually found the offending piece of code, click on this to find and instantly jump to relevant items, like subclasses, callers of methods, callees, etc.

Click on the four squares menu and find everything you need.

Breakpoints

If you’re like me, breakpoints were a way just to “po” some values. We can do a little better than that.

Use watchpoints! Stop po-ing repeatedly. Instead, use watch points to monitor when a variable’s value changes. I recently used this to debug a profile name getting updated incorrectly. There were many places it was getting updated so I just set up a breakpoint in the update function and added a watch statement to see when it received the bad value.

Conditional breakpoints. You can add code to decide when to trigger your breakpoint. This has come in handy more than I’ve expected. I’ve used it to breakpoint the tenth time something has occurred, or when a particular string is empty. To trigger it just use the menu that shows up when you right click a breakpoint marker, like this:

Breakpoint actions. I’ll admit that I don’t turn to this often, but it’s still a useful tool. We’ve used it with symbolic breakpoints to play an annoying noise whenever autolayout constraints are broken and just to print out values automatically instead of typing “po”. You can set that up like this:

Charles

Does your mobile app have a backend? If it does, you need Charles.

Charles Proxy lets you capture network calls and responses and test out different network conditions like intermittent WiFi connections. It is easily worth the $50 investment to help save you hours of debugging time. There’s a free demo available you can test out for yourself, with plenty of documentation to get you started.

Charles Proxy automatically captures individual requests on your simulator (or real device). You can see every request going in and out, allowing you to easily check the request and response formats. It’s also an infinitely better UI for synthesizing and reading the response bodies than reading from the console.

Inspecting the Yelp API Response

You can also set breakpoints on your requests and edit them before they fire off, which can be helpful if you’re deliberately trying to break something.

Example Use Case:

All the guides in the world are pretty useless if you don’t actually use these tips. I can’t debug your code for you (that’s what Stack Overflow is for), but here’s a brief example of these techniques in practice.

Problem:

We had a bug where our profile objects weren’t getting updated with the information that the user was entering in the NUX flow. I was assigned this task and had no idea where to start. Cool.

  1. Verify the network request is running using Charles.

First, I needed to check if the error was happening in the request, on the server, or in our processing in the response. To make sure that the request was happening in the first place, I used my handy Charles tool to verify that a network request was indeed being sent out to the /viewer endpoint.

2. Communicate with the team and see if the error was happening server side.

Next, I wanted to verify that the bug wasn’t something on the server side. Since this is not part of my codebase, I had to communicate with an engineer on the backend team. He was able to find a log of the attempt to update the profile object. I was told that we weren’t passing along a required header field as part of the request.

3. Search for the header code in the client side code (Command + Shift + F).

I searched on Xcode to look for the specific header that the backend engineer wanted me to have, to make sure it was even in our client side codebase. Since other requests weren’t having this problem, I figured that it was just something to do with the specific codepath of my feature. I found the code that adds the header immediately.

4. Use breakpoints to verify that the code adding the header field actually runs.

I put a breakpoint at the exact line that adds the header and attempted to trigger it again by updating the profile. And… it wasn’t hit. And this explains it! My request to update the profile didn’t go through the code path that adds the header. But I still couldn’t understand why. The line that adds the header was happening at the base Network layer, which means any network request should go through it. So what was going on?

5. Find out where we send out the network request to update the viewer

Next, I had to figure out where we even run the code to update the profile. I searched again, and this time I filtered for files that end in `Interactor.swift`. I knew we added all of our networking functionality inside of the interactor files. If I hadn’t known that, it would have taken a few more steps.

6. Read the code carefully.

What I noticed as I was inspecting the code was that one line looked a little bit different. This was my line of code:

This is another network request in the same file:

What’s going on here? We used to have an internally built Cocoapod called Novanet that we used for all our networking that this network request was still using. Back when we first made the header field a requirement, we created a Network class inside our project which adds the header field to all network requests. However, we forgot about this location when we migrated over. I changed this one line and everything started working again. Great! (Of course we had to clean up the Cocoapod, etc. afterwards. You’ve got to clean up the messes as you catch them or you’ll solve the same issues again and again.)

Takeaways:

What are the takeaways? Well, for one, proper naming and knowing how you structured the code is really step zero. The way you write your code can make a huge difference in debugging in the future. You also need to communicate with your team, especially if the issue might not be on your end. Finally, you need a structured and simple way to find and solve your problems. Knowing a few basic tools and tricks will get more done than googling fancy tricks. Utilize your search, your breakpoints, and Charles, and you should be good for most of the common problems.

--

--