How to print call stack in Swift?

SwiftDebuggingCallstack

Swift Problem Overview


In Objective-C, you can print the call stack by doing the following:

NSLog(@"%@", [NSThread callStackSymbols]);

How do you do this in Swift without using Foundation class?

Swift Solutions


Solution 1 - Swift

As Jacobson says, use the following:

##Swift 2:

print(NSThread.callStackSymbols())

##Swift 3 / Swift 4:

print(Thread.callStackSymbols)

That's Swift code. It's using a Foundation method, but so does 90%+ of what you do on iOS.

##EDIT:

Note that the formatting looks better if you use:

Thread.callStackSymbols.forEach{print($0)}

From the debugger command line you can type

e Thread.callStackSymbols.forEach{print($0)}

Solution 2 - Swift

For Swift 3 use:

print(Thread.callStackSymbols)

or for better formatting

for symbol: String in Thread.callStackSymbols {
    print(symbol)
}

Solution 3 - Swift

This improves the output a little.

for symbol: String in NSThread.callStackSymbols() {
    NSLog("%@", symbol)
}

Solution 4 - Swift

Here's a great utility class I found on github:

https://github.com/nurun/swiftcallstacktrace

You get a tuple (class,method) of any stack trace symbol so you can do a clean printout.

CallStackAnalyser.classAndMethodForStackSymbol(NSThread.callStackSymbols()[2])

Edit: swift 4.1 update

https://github.com/GDXRepo/CallStackParser

Solution 5 - Swift

I needed to write the callstack to a log file so I tweaked it like so.

var ErrorStack = String()
Thread.callStackSymbols.forEach {
    print($0)
    ErrorStack = "\(ErrorStack)\n" + $0
}

Solution 6 - Swift

 print(Thread.callStackSymbols.joined(separator: "\n"))

With this code, one can see the calls in different lines each.

 1   MyApp                               0x0000000100720780 $s9MyAppModule....
 2   CoreFoundation                      0x0000000181f04c4c EA9C1DF2-94C7-379B-BF8D-970335B1552F + 166988
 3   CoreFoundation                      0x0000000181f99554 EA9C1DF2-94C7-379B-BF8D-970335B1552F + 775508
 4   CoreFoundation                      0x0000000181f6eb34 EA9C1DF2-94C7-379B-BF8D-970335B1552F + 600884
 5   CoreFoundation                      0x0000000181f19754 _CFXNotificationPost + 696
 6   Foundation                          0x0000000183634138 86D8A58D-B71F-34C6-83E0-014B2B835F1D + 106808

Solution 7 - Swift

Thread.callStackSymbols() is nice to have. But the traceback is ugly. Demangling would be nice. The Swift 4.1+ demangler linked in @MikeS's answer is extremely comprehensive and impressive, but it's also over 4000 lines of code, overkill if you just need app, class and method, and it's quite a lot to add to a project, which I'd prefer to not to risk forgetting not to ship my app with :-)

This is a quick prototype of something that does some basic demangling of appname, class and method (which are the easy part to figure out). It's not polished. For example, it doesn't check for nil/failures in the regex ops, since it just gets a line from the callstack, which should be consistent enough to avoid problems. However, improved versions of it are welcome answers.

I added it to a class I named Debug, where I keep other debugging stuff, and invoke it from wherever in my app as:

Debug.whence()

    ... note: "where" is a Swift reserved word, and whence means basically the same thing.

It prints a line of this form (only one line, not full stack):

EventEditorViewController.configureNavigationItem():419 
<

I'll probably add an argument to take an optional object arg and then do a refined display of the object and its address without some of the parameters and syntax swift's builtin obj dump logging does, so that it would display obj info and where it is being traced.

This probably can only parse lines inside the app. It probably can't demangle non-Swift calls (like Foundation), not sure. If you need a more comprehensive demangler, check @MikeS's answer.

static func whence(_ lineNumber: Int = #line) {
    
    func matchRegex(_ matcher: String,  string : String) -> String? {
        let regex = try! NSRegularExpression(pattern: matcher, options: [])
        let range = NSRange(string.startIndex ..< string.endIndex, in: string)
        guard let textCheckingResult = regex.firstMatch(in: string, options: [], range: range) else {
            return nil
        }
        return (string as NSString).substring(with:textCheckingResult.range(at:1)) as String
    }

    func singleMatchRegex(_ matcher: String,  string : String) -> String? {
        let regex = try! NSRegularExpression(pattern: matcher, options: [])
        let range = NSRange(string.startIndex ..< string.endIndex, in: string)
        let matchRange = regex.rangeOfFirstMatch(in: string, range: range)
        if matchRange == NSMakeRange(NSNotFound, 0) {
            return nil
        }
        return (string as NSString).substring(with: matchRange) as String
    }

    var string = Thread.callStackSymbols[1]
    string = String(string.suffix(from:string.firstIndex(of: "$")!))
                    
    let appNameLenString = matchRegex(#"\$s(\d*)"#, string: string)!
    let appNameLen = Int(appNameLenString)!

    string = String(string.dropFirst(appNameLenString.count + 2))
    
    let appName = singleMatchRegex(".{\(appNameLen)}", string: string)!
    
    string = String(string.dropFirst(appNameLen))
    
    let classNameLenString = singleMatchRegex(#"\d*"#, string: string)!
    let classNameLen = Int(classNameLenString)!

    string = String(string.dropFirst(classNameLenString.count))
    
    let className = singleMatchRegex(".{\(classNameLen)}", string: string)!

    string = String(string.dropFirst(classNameLen))
    
    let methodNameLenString = matchRegex(#".(\d*)"#, string: string)!
    let methodNameLen = Int(methodNameLenString)!

    string = String(string.dropFirst(methodNameLenString.count + 1))
    
    let methodName = singleMatchRegex(".{\(methodNameLen)}", string: string)!
    
    let _ = appName
    print("\(className).\(methodName)():\(lineNumber)")
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBoonView Question on Stackoverflow
Solution 1 - SwiftDuncan CView Answer on Stackoverflow
Solution 2 - SwiftDoug AmosView Answer on Stackoverflow
Solution 3 - SwiftKevin SnowView Answer on Stackoverflow
Solution 4 - SwiftMike SView Answer on Stackoverflow
Solution 5 - SwiftBreathableView Answer on Stackoverflow
Solution 6 - SwiftVladimír SlavíkView Answer on Stackoverflow
Solution 7 - SwiftclearlightView Answer on Stackoverflow