将Error转换为NSError时,Swift 3自定义URLProtocol崩溃 [英] Swift 3 custom URLProtocol crashes when converting Error to NSError
问题描述
对于Mac OS 10.11及更高版本(使用Xcode 8.2.1),我拥有大量的Swift 3代码.有许多过程,其中包括GUI应用程序和后台服务.两者都使用自定义URLProtocol
,该自定义URLProtocol
在框架中实现(由应用程序和服务导入).协议有时可能会生成符合Error
的enum
实例,并会对其进行适当的捕获和处理(通常通过使用URLProtocolClient将其扔到尝试提出请求的URLSession上).
I've got a rather large body of Swift 3 code for Mac OS 10.11 and up (using Xcode 8.2.1). There are a number of processes, among them a GUI application and a background service. Both of these use a custom URLProtocol
, which is implemented in a framework (imported by both application and service). The protocol sometimes may generate instances of an enum
that conforms to Error
, which it catches and handles appropriately (generally by using the URLProtocolClient to toss them up to the URLSession trying to make the request).
- 没有错误时,应用程序和服务都可以正常工作.
- 出现 错误时,该应用程序可以正常运行(按预期方式运行).
- 出现错误时,服务崩溃.
- When there's no error, both the app and the service work fine.
- When there is an error, the app works fine (well, as expected).
- When there is an error, the service crashes.
在调试器中的游荡表明,当Error
由运行时自动转换为NSError
时,会发生此崩溃.我在代码中明确地添加了这个强制类型转换,并且肯定的是,我现在在那一行上遇到了同样的崩溃.
Wandering through the debugger has shown that this crash is occurring when the Error
is automatically converted into an NSError
by the runtime. I added this cast explicitly in my code, and sure enough, I get the same crash, now on that line.
I saw Swift 3.1: Crash when custom error is converted to NSError to access its domain property, but it doesn't apply here:
- 那里的解决方案-将
Error
扩展到CustomNSError
(出于良好的考虑,还扩展了LocalizedError
)并实现相关属性-并没有帮助. - 在获得
domain
之后发生崩溃;据我所知,这对我来说不是问题. - 可能与之相关:在iOS而非Mac上.
- The solution there - extending the
Error
to aCustomNSError
(andLocalizedError
for good measure) and implementing the relevant properties - didn't help. - The crash occurs after the
domain
has been obtained; as far as I can tell that was not a problem for me. - Possibly relevant: that was on iOS, not Mac.
已经尝试过列出该问题的唯一解决方案,但我有些茫然.我已经调试了好几个小时,却一无所获,只是它似乎发生在dyld
内胆的某个地方.
Having already tried the only listed solution to that question, I'm at something of a loss. I've been debugging this for hours without getting anywhere except that it seems to happen somewhere deep in the guts of dyld
.
代码:
enum CrowbarProtocolError: Error {
case FailedToCreateSocket;
case PathTooLong;
case NonSocketFile;
case SocketNotFound;
...
case UnrecognizedError(errno: Int32);
}
extension CrowbarProtocolError: LocalizedError {
public var errorDescription: String? {
return "Some localized description"
}
}
extension CrowbarProtocolError: CustomNSError {
public static var errorDomain: String {
return "Some Domain Name"
}
public var errorCode: Int {
return 204 //Should be your custom error code.
}
public var errorUserInfo: [String: Any] {
return ["WTF": "Swift?"];
}
}
...
open class CrowbarUrlProtocol: URLProtocol {
...
override open func startLoading() {
do {
let sockHandle = try CrowbarUrlProtocol.openSocket();
let req = try buildRequestData();
sockHandle.write(req);
NotificationCenter.default.addObserver(
self,
selector: #selector(self.readCompleted),
name: .NSFileHandleReadToEndOfFileCompletion,
object: nil);
sockHandle.readToEndOfFileInBackgroundAndNotify();
} catch let err {
Log.warn("CrowbarUrlProtocol request failed with error \(err)");
// -- In the background service, THIS LINE CRASHES! --
let err2 = err as NSError;
Log.warn("As NSError: \(err2)");
// -- Without the explicit cast, it crashes on this line --
self.client?.urlProtocol(self, didFailWithError: err);
}
}
...
}
我解决这个问题的一个主意是使用NSError
做所有(或尽可能多的)操作,理由是如果不需要将Error
转换为NSError
,导致崩溃的任何原因都不会发生.不知道它是否可以工作,但似乎值得尝试...
One idea I have for solving this is just doing everything (or, as much as possible) using NSError
s, on the grounds that if there's never a need to convert an Error
to an NSError
, then whatever is causing this crash won't happen. No idea if it'll work but it seems worth a try...
推荐答案
好的,据我所知这只是Swift的运行时中的一个错误,但我找到了一种解决方法:只需对所有涉及的内容使用NSError
Obj-C代码而不是Swift Error
(以避免隐式强制转换).由于我已经实现了CustomNSError
,因此很容易在我的Error
枚举上创建一个toNSError()
函数,并将其用于self.client?.urlProtocol(self, didFailWithError: err)
行.
OK, as far as I can tell this is just a bug in Swift's runtime, but I found a work-around: just use NSError
for everything involving Obj-C code rather than Swift Error
s (to avoid the implicit cast). Since I already was implementing CustomNSError
, it was easy to just create a toNSError()
function on my Error
enum, and use that for the self.client?.urlProtocol(self, didFailWithError: err)
lines.
enum CrowbarProtocolError: Error, CustomNSError {
case FailedToCreateSocket;
case PathTooLong;
...
public func asNSError() -> NSError {
return NSError(domain: CrowbarProtocolError.errorDomain,
code: self.errorCode,
userInfo: self.errorUserInfo);
}
}
...
open class CrowbarUrlProtocol: URLProtocol {
...
override open func startLoading() {
do {
let sockHandle = try CrowbarUrlProtocol.openSocket();
let req = try buildRequestData();
sockHandle.write(req);
NotificationCenter.default.addObserver(
self,
selector: #selector(self.readCompleted),
name: .NSFileHandleReadToEndOfFileCompletion,
object: nil);
sockHandle.readToEndOfFileInBackgroundAndNotify();
} catch let err as CrowbarProtocolError {
Log.warn("CrowbarUrlProtocol request failed with error \(err)");
self.client?.urlProtocol(self, didFailWithError: err.asNSError());
}
catch let err {
Log.warn("CrowbarUrlProtocol caught non-CrowbarProtocol Error \(err)");
// This would probably crash the service, but shouldn't ever happen
self.client?.urlProtocol(self, didFailWithError: err);
}
}
...
}
这篇关于将Error转换为NSError时,Swift 3自定义URLProtocol崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!