Konwersja ErrorType na NSError powoduje utratę powiązanych obiektów

W Swift 2.0 NSError jest zgodny z protokołem ErrorType.

Dla niestandardowo zdefiniowanego błędu, możemy określić obiekt(y) powiązujący (- E) w niektórych przypadkach, jak poniżej.

enum LifeError: ErrorType {
    case BeBorn
    case LostJob(job: String)
    case GetCaughtByWife(wife: String)
    ...
}

Możemy wygodnie wykonać następujące czynności:

do {
    try haveAffairWith(otherPerson)
} catch LifeError.GetCaughtByWife(let wife) {
    ...
}

Jeśli jednak chcemy, aby przeszła w inne miejsca jako NSError, traci informacje o obiektach powiązanych.

println("\(LifeError.GetCaughtByWife("Name") as NSError)")

Druki:

Error Domain=... Code=1 "The operation couldn't be completed". (... error 1)

I jego userInfo jest nil.

Gdzie moje wife jest związane z ErrorType?

Author: Moritz, 2015-07-15

7 answers

Nowość w Xcode 8: CustomNSError protokół .

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]
        }
    }
}
 40
Author: Igor Camilo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-30 17:21:20

An ErrorType naprawdę nie można rzucić na NSError, musisz wziąć Powiązane Dane i spakować je do NSError.

do {
    try haveAffairWith(otherPerson)
} catch LifeError.GetCaughtByWife(let wife) {
    throw NSError(domain:LifeErrorDomain code:-1 userInfo:
        [NSLocalizedDescriptionKey:"You cheated on \(wife)")
}

EDIT: w rzeczywistości możesz wykonać cast od ErrorType do NSError, ale NSError otrzymany z domyślnej implementacji jest dość prymitywny. To, co robię w mojej aplikacji, to zaczepianie aplikacji: willPresentError: w delegacie aplikacji i używanie niestandardowej klasy, aby odczytać ErrorType mojej aplikacji i udekorować NSErrors, aby powrócić.

 23
Author: iluvcapra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-28 19:53:22

Tworzenie NSError w każdym bloku catch może prowadzić do dużej ilości kopiowania i wklejania, aby przekonwertować własne ErrorType na NSError. Usunąłem to podobnie jak @ powertoold.

protocol CustomErrorConvertible {
    func userInfo() -> Dictionary<String,String>?
    func errorDomain() -> String
    func errorCode() -> Int
}

To rozszerzenie może zawierać kod, co jest wspólne dla LifeError, które już mamy i innych niestandardowych typów błędów, które możemy utworzyć.

extension CustomErrorConvertible {
    func error() -> NSError {
        return NSError(domain: self.errorDomain(), code: self.errorCode(), userInfo: self.userInfo())
    }
}

Do realizacji!

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

I tak go używać.

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

Możesz łatwo przenieść niektóre funkcje, takie jak func userInfo() -> Dictionary<String,String>? z LifeError do extension CustomErrorConvertible lub innego rozszerzenia.

Zamiast kodowania na twardo kody błędów, jak powyżej, enum może być lepsze.

enum LifeError:Int {
  case Born
  case LostJob
}
 14
Author: orkoden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-30 17:23:55

Moim rozwiązaniem tego problemu było stworzenie enum zgodnego z Int, ErrorType:

enum AppError: Int, ErrorType {
    case UserNotLoggedIn
    case InternetUnavailable
}

A następnie rozszerzyć enum, aby było zgodne z CustomStringConvertible i niestandardowym protokołem o nazwie CustomErrorConvertible:

extension AppError: CustomStringConvertible, CustomErrorConvertible

protocol CustomErrorConvertible {
    var error: NSError { get }
}

Dla opisu i błędu włączyłem AppError. Przykład:

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
            }

A potem skomponowałem swój własny NSError:

return NSError(domain:NSBundle.mainBundle().bundleIdentifier!, code:errorCode, userInfo:[NSLocalizedDescriptionKey: errorDescription])
 6
Author: powertoold,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-04 17:48:47

Ja też mam ten problem używając PromiseKit i znalazłem obejście, które może być trochę brzydkie, ale wydaje się działać.

Wklejam tutaj mój plac zabaw, abyś mógł zobaczyć cały proces.

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()
 2
Author: Charly Liu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-13 16:08:09

Najlepszym rozwiązaniem, jakie znalazłem, jest posiadanie Objective - C wrappera do odlewania ErrorType do NSError (przez NSObject* parmetr) i ekstrakcji userInfo. Najprawdopodobniej zadziała to również w przypadku innych powiązanych obiektów.

W moim przypadku wszystkie inne próby użycia tylko Swifta zaowocowały uzyskaniem nil userInfo.

Oto Helper Objective-C. Umieść go na przykład w MyErrorUtils klasie narażonej na Swift:

+ (NSDictionary*)getUserInfo:(NSObject *)error {
    NSError *nsError = (NSError *)error;
    if (nsError != nil) {
        return [nsError userInfo];
    } else {
        return nil;
    }
}

Następnie użyj helpera w języku Swift w następujący sposób:

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 ...
    }

}

(I ' m obecnie używa XCode 8 i Swift 2.3)

 1
Author: Peter Lamberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-28 12:59:23

Jak wskazała przyjęta odpowiedź, jest teraz CustomNSError W Swift 3, jednak niekoniecznie musisz go używać. Jeśli zdefiniujesz swój typ błędu w ten sposób

@objc
enum MyErrorType: Int, Error { ... }

Wtedy ten błąd można bezpośrednio rzucić na NSError:

let error: MyErrorType = ...
let objcError = error as NSError
Właśnie odkryłem to dzisiaj i choć dzielę się tym ze światem.
 1
Author: Mecki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-30 18:01:29