program tip

ErrorType을 NSError로 변환하면 관련 객체가 손실됩니다.

radiobox 2020. 12. 25. 09:09
반응형

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있습니까?


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

ErrorType정말로 주조 할 수없는 NSError, 당신은 관련 데이터를 받아와로 패키지해야 NSError자신을.

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

편집 : 실제로에서 ErrorType으로 캐스트 할 수 NSError있지만 NSError기본 구현에서 얻는 것은 매우 원시적입니다. 내 앱에서 내가하는 일은 내 앱 델리게이트에서 application : willPresentError : 후킹하고 사용자 지정 클래스를 사용하여 내 앱을 읽고 ErrorTypeNSErrors를 장식하여 반환하는 것입니다.


NSError모든 catch 블록에서 생성하면 사용자 정의 ErrorTypeNSError. @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>?에서 LifeErrorextension 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 }
}

설명과 오류에 대해 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])

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

The best solution that I found, is to have an Objective-C wrapper for casting the ErrorType to NSError (via NSObject* parmeter) and extracting the userInfo. Most likely this would work for other associated objects too.

In my case all other attempts using only Swift resulted in getting a nil userInfo.

Here is the Objective-C helper. Place it for example in a MyErrorUtils class exposed to Swift:

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

Then use the helper in Swift like this:

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 currently using XCode 8 and Swift 2.3)


As the accepted answer pointed out, there's now CustomNSError in Swift 3, however, you don't necessarily need to use it. If you define your error type like this

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

Then this error can directly be casted to NSError:

let error: MyErrorType = ...
let objcError = error as NSError

I just discovered that today and though I share it with the world.

ReferenceURL : https://stackoverflow.com/questions/31422005/converting-errortype-to-nserror-loses-associated-objects

반응형