Преобразование ErrorType в NSError теряет связанные объекты
В Swift 2.0 NSError
соответствует ErrorType
протокол.
для специально определенной ошибки мы можем указать ассоциирующий объект(ы) для некоторых случаев, как показано ниже.
enum LifeError: ErrorType {
case BeBorn
case LostJob(job: String)
case GetCaughtByWife(wife: String)
...
}
мы можем удобно сделать следующее:
do {
try haveAffairWith(otherPerson)
} catch LifeError.GetCaughtByWife(let wife) {
...
}
однако, если мы хотим, чтобы он перешел в другие места как NSError
, он теряет свою ассоциирующую информацию об объекте.
println("(LifeError.GetCaughtByWife("Name") as NSError)")
принты:
Error Domain=... Code=1 "The operation couldn't be completed". (... error 1)
и userInfo
is nil
.
где мой wife
связан с ErrorType
?
7 ответов:
новое в Xcode 8:
CustomNSError
протокол.enum LifeError: CustomNSError { case beBorn case lostJob(job: String) case getCaughtByWife(wife: String) static var errorDomain: String { return "LifeError" } var errorCode: Int { switch self { case .beBorn: return 0 case .lostJob(_): return 1 case .getCaughtByWife(_): return 2 } } var errorUserInfo: [String : AnyObject] { switch self { case .beBorn: return [:] case .lostJob(let job): return ["Job": job] case .getCaughtByWife(let wife): return ["Wife": wife] } } }
An
ErrorType
на самом деле не может быть приведено кNSError
, вы должны взять соответствующие данные и упаковать его вNSError
себя.do { try haveAffairWith(otherPerson) } catch LifeError.GetCaughtByWife(let wife) { throw NSError(domain:LifeErrorDomain code:-1 userInfo: [NSLocalizedDescriptionKey:"You cheated on \(wife)") }
EDIT: на самом деле вы можете сделать бросок из
ErrorType
toNSError
, аNSError
вы получаете от реализации по умолчанию довольно примитивно. То, что я делаю в своем приложении, - это приложение hooking:willPresentError: в моем делегате приложения и с помощью пользовательского класса для чтения моего приложенияErrorType
' s и украсить NSErrors, чтобы вернуться.
создания
NSError
в каждом блоке catch может привести к большому количеству копирования и вставки для преобразования пользовательскихErrorType
доNSError
. Я абстрагировал его, похожий на @powertoold.protocol CustomErrorConvertible { func userInfo() -> Dictionary<String,String>? func errorDomain() -> String func errorCode() -> Int }
это расширение может содержать код, который является общим для
LifeError
у нас уже есть и другие пользовательские типы ошибок, которые мы можем создать.extension CustomErrorConvertible { func error() -> NSError { return NSError(domain: self.errorDomain(), code: self.errorCode(), userInfo: self.userInfo()) } }
выкл к реализации!
enum LifeError: ErrorType, CustomErrorConvertible { case BeBorn case LostJob(job: String) case GetCaughtByPolice(police: String) func errorDomain() -> String { return "LifeErrorDomain" } func userInfo() -> Dictionary<String,String>? { var userInfo:Dictionary<String,String>? if let errorString = errorDescription() { userInfo = [NSLocalizedDescriptionKey: errorString] } return userInfo } func errorDescription() -> String? { var errorString:String? switch self { case .LostJob(let job): errorString = "fired as " + job case .GetCaughtByPolice(let cops): errorString = "arrested by " + cops default: break; } return errorString } func errorCode() -> Int { switch self { case .BeBorn: return 1 case .LostJob(_): return -9000 case .GetCaughtByPolice(_): return 50 } } }
и вот как его использовать.
func lifeErrorThrow() throws { throw LifeError.LostJob(job: "L33tHax0r") } do { try lifeErrorThrow() } catch LifeError.BeBorn { print("vala morgulis") } catch let myerr as LifeError { let error = myerr.error() print(error) }
вы могли легко перемещать определенные функции, такие как
func userInfo() -> Dictionary<String,String>?
СLifeError
доextension CustomErrorConvertible
или другое расширение.вместо жесткого кодирования коды ошибок, как выше перечисление может быть предпочтительным.
enum LifeError:Int { case Born case LostJob }
мое решение этой проблемы состояло в том, чтобы создать перечисление, которое соответствует Int, ErrorType:
enum AppError: Int, ErrorType { case UserNotLoggedIn case InternetUnavailable }
а затем расширить перечисление, чтобы соответствовать CustomStringConvertible и пользовательский протокол называется CustomErrorConvertible:
extension AppError: CustomStringConvertible, CustomErrorConvertible protocol CustomErrorConvertible { var error: NSError { get } }
для описания и ошибки, я включил Aperror. Пример:
Description: switch self { case .UserNotLoggedIn: return NSLocalizedString("ErrorUserNotLoggedIn", comment: "User not logged into cloud account.") case .InternetUnavailable: return NSLocalizedString("ErrorInternetUnavailable", comment: "Internet connection not available.") } Error: switch self { case .UserNotLoggedIn: errorCode = UserNotLoggedIn.rawValue; errorDescription = UserNotLoggedIn.description case .InternetUnavailable: errorCode = InternetUnavailable.rawValue; errorDescription = InternetUnavailable.description }
и тогда я сочинил свой собственный NSError:
return NSError(domain:NSBundle.mainBundle().bundleIdentifier!, code:errorCode, userInfo:[NSLocalizedDescriptionKey: errorDescription])
У меня тоже есть эта проблема, используя PromiseKit, и я нашел обходной путь, который может быть немного уродливым, но, похоже, работает.
я вставляю сюда свою игровую площадку, чтобы вы могли видеть весь процесс.
import Foundation import PromiseKit import XCPlayground let error = NSError(domain: "a", code: 1, userInfo: ["hello":"hello"]) // Only casting won't lose the user info let castedError = error as ErrorType let stillHaveUserInfo = castedError as NSError // when using promises func convert(error: ErrorType) -> Promise<Int> { return Promise<Int> { (fulfill, reject) in reject(error) } } let promiseA = convert(error) // Seems to lose the user info once we cast back to NSError promiseA.report { (promiseError) -> Void in let lostUserInfo = promiseError as NSError } // Workaround protocol CastingNSErrorHelper { var userInfo: [NSObject : AnyObject] { get } } extension NSError : CastingNSErrorHelper {} promiseA.report { (promiseError) -> Void in let castingNSErrorHelper = promiseError as! CastingNSErrorHelper let recoveredErrorWithUserInfo = castingNSErrorHelper as! NSError } XCPSetExecutionShouldContinueIndefinitely()
лучшее решение, которое я нашел, это иметь Objective-C обертку для литья
ErrorType
доNSError
(черезNSObject*
parmeter) и извлеченияuserInfo
. Скорее всего, это будет работать и для других связанных объектов.в моем случае все остальные попытки с использованием только Swift привели к получению
nil
userInfo
.вот цель-c помощник. Поместите его, например, в
MyErrorUtils
класс, подверженный Swift:+ (NSDictionary*)getUserInfo:(NSObject *)error { NSError *nsError = (NSError *)error; if (nsError != nil) { return [nsError userInfo]; } else { return nil; } }
затем используйте помощник в Быстро, как это:
static func myErrorHandler(error: ErrorType) { // Note the as? cast to NSObject if let userInfo: [NSObject: AnyObject]? = MyErrorUtils.getUserInfo(error as? NSObject) { let myUserInfo = userInfo["myCustomUserInfo"] // ... Error processing based on userInfo ... } }
(в настоящее время я использую XCode 8 и Swift 2.3)
как указал принятый ответ, теперь есть
CustomNSError
в Swift 3, однако, вам не обязательно использовать его. Если вы определяете свой тип ошибки следующим образом@objc enum MyErrorType: Int, Error { ... }
затем эта ошибка может быть непосредственно преобразован в
NSError
:let error: MyErrorType = ... let objcError = error as NSError
Я только что обнаружил, что сегодня, и хотя я разделяю его с миром.