提问人:Tiny Tim 提问时间:2/18/2023 最后编辑:Ken WhiteTiny Tim 更新时间:2/19/2023 访问量:74
是否可以旋转关键字“抛出”?
is it possible to swizzle the keyword `throw`?
问:
我正在考虑错误处理,最近我学会了旋转。Swizzling 当然是一个不应该经常使用的工具,我想我理解这一点,但它让我想知道。如果每当抛出错误时,如果我想捕获抛出的错误。有没有办法使用swizzling或类似的东西来拦截错误并将其记录在某个地方而不会中断应用程序的流程?我正在考虑可能旋转关键字,但这可能不起作用。什么工具会用在这种事情上?throws
答:
不,你不能。许多编译器检查都依赖于“中断应用程序流”这一事实。例如,如果代码的某个路径,则该路径不需要:throw
throw
return
func foo(x: Bool) throws -> Int {
if x {
throw someError
} else {
return 1
}
}
现在,如果没有“中断应用程序的流程”,以下代码中会打印什么?throw someError
bar
func bar() throws {
let x = try foo(x: true)
print(x)
}
另一个例子是:guard
guard let value = somethingThatMayBeNil else {
throw someError
}
print(value.nowICanSafelyAccessThis)
如果上面实际上没有“中断应用程序的流程”并停止运行该方法的其余部分,那么 将发生非常糟糕的事情,因为 是零,我什至不确定是否存在。throw someError
print(value.nowICanSafelyAccessThis)
somethingThatMayBeNil
value
关键是,这会将调用堆栈展开到可以捕获错误的点,并且不会执行依赖于没有错误的代码。throw
如果要以某种方式“捕获”错误,可以使用结果
。第一个例子可以变成:
func foo(x: Bool) -> Result<Int, Error> {
if x {
return Result.failure(someError)
} else {
return Result.success(1)
}
}
func bar() {
let x = foo(x: true)
// now this will print "failure(...)"
print(x)
// and x is of type Result<Int, Error>, rather than Int
switch x {
case .failure(let e):
// log the error e here...
case .success(let theInt):
// do something with theInt...
}
}
您还可以使用 init(catching:)
将任何抛出调用包装到 .假设如果你不能改变,那么你可以改为这样做:Result
foo
bar
func bar() {
let x = Result { try foo(x: true) }
...
第二个例子可以变成:
guard let value = somethingThatMayBeNil else {
// assuming the return type is changed appropriately
return Result.failure(someError)
}
print(value.nowICanSafelyAccessThis)
请注意,这仍然会“中断应用程序的流程”,因为如果为 nil,则仍未执行。你对此无能为力。print
somethingThatMayBeNil
您还可以添加一个方便的工厂方法来记录:
extension Result {
static func failureAndLog(_ error: Failure) -> Result {
// do your logging here...
return .failure(error)
}
}
评论
Result
async throws
Result
不,你不能摇晃.但是 Swift 运行时有一个钩子,_swift_WillThrow
,它可以让你在它即将被抛出的那一刻检查它。这个钩子不是一个稳定的 API,可以在未来的 Swift 版本中更改或删除。throw
Error
如果您使用的是 Xcode 14.3(现在的 beta 版本)中包含的 Swift 5.8,则可以使用 _swift_setWillThrowHandler
函数来设置该函数:_swift_willThrow
@_silgen_name("_swift_setWillThrowHandler")
func setWillThrowHandler(
_ handler: (@convention(c) (UnsafeRawPointer) -> Void)?
)
var errors: [String] = []
func tryItOut() {
setWillThrowHandler {
let error = unsafeBitCast($0, to: Error.self)
let callStack = Thread.callStackSymbols.joined(separator: "\n")
errors.append("""
\(error)
\(callStack)
""")
}
do {
throw MyError()
} catch {
print("caught \(error)")
print("errors = \(errors.joined(separator: "\n\n"))")
}
}
输出:
caught MyError()
errors = MyError()
0 iPhoneStudy 0x0000000102a97d9c $s11iPhoneStudy8tryItOutyyFySVcfU_ + 252
1 iPhoneStudy 0x0000000102a97ff0 $s11iPhoneStudy8tryItOutyyFySVcfU_To + 12
2 libswiftCore.dylib 0x000000018c2f4ee0 swift_willThrow + 56
3 iPhoneStudy 0x0000000102a978f8 $s11iPhoneStudy8tryItOutyyF + 160
4 iPhoneStudy 0x0000000102a99740 $s11iPhoneStudy6MyMainV4mainyyFZ + 28
5 iPhoneStudy 0x0000000102a997d0 $s11iPhoneStudy6MyMainV5$mainyyFZ + 12
6 iPhoneStudy 0x0000000102a99f48 main + 12
7 dyld 0x0000000102d15514 start_sim + 20
8 ??? 0x0000000102e11e50 0x0 + 4343275088
9 ??? 0x9f43000000000000 0x0 + 11476016275470155776
如果你使用的是较旧的 Swift(但我认为至少是 Swift 5.2,它在 Xcode 11.4 中),你必须直接访问_swift_willThrow
钩子:
var swift_willThrow: UnsafeMutablePointer<(@convention(c) (UnsafeRawPointer) -> Void)?> {
get {
dlsym(UnsafeMutableRawPointer(bitPattern: -2), "_swift_willThrow")!
.assumingMemoryBound(to: (@convention(c) (UnsafeRawPointer) -> Void)?.self)
}
}
var errors: [String] = []
func tryItOut() {
swift_willThrow.pointee = {
let error = unsafeBitCast($0, to: Error.self)
let callStack = Thread.callStackSymbols.joined(separator: "\n")
errors.append("""
\(error)
\(callStack)
""")
}
do {
throw MyError()
} catch {
print("caught \(error)")
print("errors = \(errors.joined(separator: "\n\n"))")
}
}
评论
dynamic @objc func
throw