如何在 Swift 中打印调用堆栈?

在 Objective-C 中,您可以通过执行以下操作来打印调用堆栈:

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

在 Swift 中如何在不使用基础类的情况下做到这一点?

33600 次浏览

正如雅各布森所说,使用以下方法:

斯威夫特2:

print(NSThread.callStackSymbols())

斯威夫特3/斯威夫特4:

print(Thread.callStackSymbols)

这是 Swift 的代码,它使用的是 Foundation 方法,但是90% 以上的 iOS 操作系统也是如此。

编辑:

注意,如果使用:

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

从调试器命令行中键入

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

这稍微提高了输出。

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

下面是我在 github 上找到的一个很棒的实用类:

Https://github.com/nurun/swiftcallstacktrace

您可以获得任何堆栈跟踪符号的 tuple (类、方法) ,这样就可以进行干净的打印输出。

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

编辑: Swift 4.1更新

Https://github.com/gdxrepo/callstackparser

对于 Swift 3的使用:

print(Thread.callStackSymbols)

或者更好的格式

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

我需要将调用堆栈写入一个日志文件,所以我这样调整了它。

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

通过这段代码,可以看到每行中的调用。

 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

CallStackSymbols ()是很好的选择。但是线索很难追踪。把衣服脱掉就好了。在@MikeS 的回答中链接的 Swift 4.1 + 驱动程序是非常全面和令人印象深刻的,但它也有超过4000行的代码,如果你只是需要应用程序、类和方法,那就太过分了,而且它对于一个项目来说增加了很多东西,我宁愿不要忘记不要把我的应用程序发布到: -)

这是一个快速原型,可以对 appname、 class 和 method 进行一些基本的分离(这是很容易理解的部分)。没有打磨过。例如,它不检查正则表达式操作中是否存在空/故障,因为它只是从调用堆栈中获取一行,这一行应该足够一致以避免问题。然而,它的改进版本是受欢迎的答案。

我将它添加到一个名为 调试的类中,在这个类中我保留了其他调试内容,并在应用程序的任何地方调用它:

Debug.whence()

注意: “ where”是一个 Swift 保留字,因此它的意思基本上是一样的

它打印一行此表单(只有一行,不是完整堆栈) :

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)")
}