How to print call stack in Swift?
Asked Answered
E

7

79

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?

Excruciating answered 10/6, 2015 at 11:20 Comment(5)
Autocompletion in playground gives me: NSLog(NSThread.callStackSymbols()).Reproval
Sorry, I should clarify - without using Foundation class.Excruciating
By using Foundation classes. It's not evil. Much of the power of Cocoa/Cocoa touch is built around the Foundation classes. Use them when needed.Floristic
@DuncanC Agree but want to know a way for a strictly Swift code.Excruciating
There is no way I know of to do this without using Foundation classes. There is no way I know of to write an iOS or Mac OS GUI app without using Foundation Classes. You can call Foundation Class methods using purely Swift code, but you still need to use Foundation Classes.Floristic
F
130

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)}
Floristic answered 13/6, 2015 at 2:25 Comment(6)
that works great, but I get a messed up layout in the console window, looks like some tab / whitespace issue. Using Xcode 7.2Rafaelrafaela
that seems to work, is there a way to get a more readable stack trace, like when you put a breakpoint in Xcode you can see the function calls with their parameters just like in the swift source file, but the 'dump' gives a less readable version with addresses etcRafaelrafaela
@AlexMan, As of Swift 3, yes. However, this post was made before Swift 3 was released.Floristic
I'm getting this: error: unknown command shorthand suffix: '(Thread.callStackSymbols)' error: Unrecognized command 'print(Thread.callStackSymbols)'.Ballinger
@Kingalione, if you're trying to type it in the debugger, you need to type e print(Thread.callStackSymbols) or e Thread.callStackSymbols.forEach{print($0)}. e is short for expression, the LLDB command that tells the debugger to evaluate what you type as Swift statement.Floristic
You may also need to do some additional work in case when callstack's symbols are not readable. github.com/GDXRepo/CallStackParser - use it as an example.Arboreal
U
14

For Swift 3 use:

print(Thread.callStackSymbols)

or for better formatting

for symbol: String in Thread.callStackSymbols {
    print(symbol)
}
Unlimber answered 22/9, 2016 at 7:49 Comment(1)
or even more swift 3: Thread.callStackSymbols.forEach { print($0) }Guinevere
G
9
 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
Granada answered 9/2, 2022 at 14:58 Comment(1)
While this code snippet may solve the question, including an explanation will help people understand the reasons for your code suggestion.Laos
B
6

This improves the output a little.

for symbol: String in NSThread.callStackSymbols() {
    NSLog("%@", symbol)
}
Biarritz answered 25/2, 2016 at 13:40 Comment(1)
Why not use Swift's print in the for loop to display each line rather than NSLog? NSLog is quite "noisy" (every line starts with a timestamp, info about the process and thread, etc.)Floristic
P
2

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

Palpitation answered 24/5, 2016 at 15:10 Comment(3)
Unfortunately this doesn't work with Swift 3, and it looks like there is no easy way to "demangle" function names with Swift 3. Although this project might be a possibility (haven't tried it myself): github.com/mattgallagher/CwlDemangleNall
looks like someone made some effort to make it work with newer versions of Swift: github.com/GDXRepo/CallStackParserMcnamara
Doesn't work with Swift 5+Cockpit
U
2

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)")
}
Unlikely answered 6/5, 2022 at 5:55 Comment(0)
P
1

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
}
Pseudonymous answered 26/5, 2018 at 19:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.