When debugger fails - true story

Photo by James Harrison / Unsplash

Every developer has this one day, one day when he questions every life choice that leads to this moment. I had this day.

I received a bug ticket - one of the requests that are sent from our app has a different header, one value doesn't match other requests. Easy peasy lemon squeezy.

I started with a simple string search for the parameter name. No results. No results? What the heck, impossible. OK, I need to bring big guns. I'll just add a symbolic breakpoint for one method - NSMutableURLRequest addValue:forHTTPHeaderField:. From there I can see which library and what part of code is altering the header.

Adding symbolic breakpoint is very easy in Xcode. In the breakpoints tab, tap + and just put whole method signature, or just parameter name, the debugger will automatically find all matches, It will, but it couldn't for this one simple method!

Symbolic breakpoints

Can you see how it finds all matches for viewDidLoad, but none for addValue. How? This method exists, it has documentation. I will not yield. Maybe something is not loading? I'll use the debugger command - image lookup -r -n to find symbols.

Here it is, between all symbols, I knew I'll find You!

1 match found in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/swift/libswiftFoundation.dylib:
Address: libswiftFoundation.dylib[0x0000000000127500] (libswiftFoundation.dylib.__TEXT.__text + 1202608)
Summary: libswiftFoundation.dylib`Foundation.URLRequest.addValue(_: Swift.String, forHTTPHeaderField: Swift.String) -> ()

So, I know the symbol exists, I know it is loaded, but breakpoint doesn't work! I have tried everything, adding breakpoint from the command line, every name and parameter combination, module names, everything. I was close to giving up. Then, the light bulb. I'll use swizzling!

Let's add a small extension to the mutable request:

extension NSMutableURLRequest {
    @objc func myAddHeader(_ value: String, forHTTPHeaderField field: String) {
        print("myAddHeader")
    }
}

Now we can exchange implementations. I made my changes on the app start code:

let instance = NSMutableURLRequest()
        let aClass: AnyClass! = object_getClass(instance)
        let originalMethod = class_getInstanceMethod(aClass, #selector(NSMutableURLRequest.addValue(_:forHTTPHeaderField:)))
        let newMethod = class_getInstanceMethod(aClass, #selector(NSMutableURLRequest.myAddHeader(_:forHTTPHeaderField:)))
        method_exchangeImplementations(originalMethod!, newMethod!)

This will dynamically switch the implementation of the method in question. Now we can add breakpoint inside `myAddHeader` and run the project.

working breakpoint

Success! Breakpoint works and now I know what library makes the call, I can see the whole call stack, all I need to fix the problem.

As you can see, there are many ways of dealing with problems, sometimes we just need to think out of the box:)

Artur Gruchała

Artur Gruchała

I started learning iOS development when Swift was introduced. Since then I've tried Xamarin, Flutter, and React Native. Nothing is better than native code:)
Poland