Преобразование 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
- nil
.
Где мой wife
, связанный с ErrorType
?
Ответы
Ответ 1
Новое в 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]
}
}
}
Ответ 2
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
до NSError
, но NSError
, который вы получаете от реализации по умолчанию, довольно примитивен. То, что я делаю в своем приложении, - это подключить приложение: willPresentError: в моем делете делегата и использовать специальный класс для чтения моего приложения ErrorType
и украсить NSErrors для возврата.
Ответ 3
Создание 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
}
Ответ 4
Мое решение этой проблемы состояло в создании перечисления, которое соответствует Int, ErrorType:
enum AppError: Int, ErrorType {
case UserNotLoggedIn
case InternetUnavailable
}
И затем расширьте перечисление, чтобы он соответствовал CustomStringConvertible и настраиваемому протоколу CustomErrorConvertible:
extension AppError: CustomStringConvertible, CustomErrorConvertible
protocol CustomErrorConvertible {
var error: NSError { get }
}
Для описания и ошибки я включил AppError. Пример:
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])
Ответ 5
У меня тоже проблема с использованием 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()
Ответ 6
Лучшим решением, которое я нашел, является наличие обертки Objective-C для отбрасывания ErrorType
до NSError
(через NSObject*
parmeter) и извлечение userInfo
. Скорее всего, это будет работать и для других связанных объектов.
В моем случае все другие попытки с использованием только Swift привели к получению nil
userInfo
.
Вот помощник Objective-C. Поместите его, например, в класс MyErrorUtils
, подверженный Swift:
+ (NSDictionary*)getUserInfo:(NSObject *)error {
NSError *nsError = (NSError *)error;
if (nsError != nil) {
return [nsError userInfo];
} else {
return nil;
}
}
Затем используйте помощник в Swift следующим образом:
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)
Ответ 7
Как отметил принятый ответ, теперь есть CustomNSError
в Swift 3, однако вам не обязательно его использовать. Если вы определяете свой тип ошибки, как этот
@objc
enum MyErrorType: Int, Error { ... }
Затем эту ошибку можно напрямую передать на NSError
:
let error: MyErrorType = ...
let objcError = error as NSError
Я только что обнаружил, что сегодня и хотя я разделяю его с миром.