[swift] 신속한 언어의 오류 처리

나는 Swift를 너무 많이 읽지 않았지만 주목할 점은 예외가 없다는 것입니다. 그렇다면 Swift에서 오류 처리는 어떻게합니까? 누구든지 오류 처리와 관련된 것을 발견 했습니까?



답변

스위프트 2 & 3

새로운 오류 처리 메커니즘이 있기 때문에 예외와 다소 유사하지만 세부 사항이 다르기 때문에 Swift 2에서는 상황이 약간 변경되었습니다.

1. 오류 가능성 표시

함수 / 메소드에 오류가 발생할 수 있음을 나타내려면 다음 throws과 같은 키워드 를 포함해야합니다.

func summonDefaultDragon() throws -> Dragon

참고 : 함수가 실제로 던질 수있는 오류 유형에 대한 사양은 없습니다. 이 선언은 단순히 함수가 ErrorType을 구현하는 모든 유형의 인스턴스를 던질 수 있거나 전혀 던지지 않음을 나타냅니다.

2. 오류를 발생시킬 수있는 함수 호출

함수를 호출하려면 다음과 같이 try 키워드를 사용해야합니다.

try summonDefaultDragon()

이 줄은 일반적으로 이와 같은 do-catch 블록이 있어야합니다

do {
    let dragon = try summonDefaultDragon()
} catch DragonError.dragonIsMissing {
    // Some specific-case error-handling
} catch DragonError.notEnoughMana(let manaRequired) {
    // Other specific-case error-handlng
} catch {
    // Catch all error-handling
}

참고 : catch 절은 Swift 패턴 일치의 모든 강력한 기능을 사용하므로 여기에서 매우 유연합니다.

throws키워드 자체로 표시된 함수에서 throwing 함수를 호출하는 경우 오류를 전파하기로 결정할 수 있습니다 .

func fulfill(quest: Quest) throws {
    let dragon = try summonDefaultDragon()
    quest.ride(dragon)
} 

또는 다음을 사용하여 투사 기능을 호출 할 수 있습니다 try?.

let dragonOrNil = try? summonDefaultDragon()

이렇게하면 오류가 발생하면 반환 값을 얻거나 nil을 얻습니다. 이 방법을 사용하면 오류 개체가 나타나지 않습니다.

즉, 다음 try?과 같은 유용한 문장과 결합 할 수도 있습니다 .

if let dragon = try? summonDefaultDragon()

또는

guard let dragon = try? summonDefaultDragon() else { ... }

마지막으로, 실제로 오류가 발생하지 않는다는 것을 알 수 있으며 (예 : 이미 확인한 전제 조건이므로) try!키워드를 사용하십시오 .

let dragon = try! summonDefaultDragon()

함수에서 실제로 오류가 발생하면 응용 프로그램에 런타임 오류가 발생하고 응용 프로그램이 종료됩니다.

3. 오류 발생

오류를 발생시키기 위해 다음과 같이 throw 키워드를 사용하십시오

throw DragonError.dragonIsMissing

ErrorType프로토콜에 맞는 것을 던질 수 있습니다 . 초보자 NSError는이 프로토콜을 준수하지만 열거 형 ErrorType을 사용하여 여러 관련 오류를 잠재적으로 다음과 같은 추가 데이터 조각으로 그룹화 할 수 있습니다.

enum DragonError: ErrorType {
    case dragonIsMissing
    case notEnoughMana(requiredMana: Int)
    ...
}

새로운 Swift 2 & 3 오류 메커니즘과 Java / C # / C ++ 스타일 예외의 주요 차이점은 다음과 같습니다.

  • 구문은 약간 다릅니다 : do-catch+ try+ defer와 기존 try-catch-finally구문.
  • 예외 처리는 일반적으로 성공 경로보다 예외 경로에서 훨씬 더 높은 실행 시간을 발생시킵니다. 성공 경로와 오류 경로의 비용이 거의 같은 Swift 2.0 오류의 경우에는 그렇지 않습니다.
  • 모든 오류 발생 코드를 선언해야하지만 예외는 어디에서나 발생했을 수 있습니다. Java 명명법에서는 모든 오류가 “체크 된 예외”입니다. 그러나 Java와 달리 잠재적으로 발생 된 오류를 지정하지 않습니다.
  • 스위프트 예외는 ObjC 예외와 호환되지 않습니다. 당신의 do-catch당신이 ObjC를 사용해야합니다 그것을 위해 블록은, 반대로 어떤 NSException, 그리고 그 반대를 잡을 수 없습니다.
  • Swift 예외는 ( 함수 를 리턴 하기 위해 ) 또는 ( 함수 를 리턴 하기 위해) 리턴 하고 오류 세부 사항을 전달하는 Cocoa NSError메소드 규칙 과 호환됩니다 .falseBoolnilAnyObjectNSErrorPointer

오류 처리를 용이하게하는 여분의 합성 설탕으로서 두 가지 개념이 더 있습니다

  • deferJava / C # / etc의 finally 블록과 동일한 효과를 얻을 수있는 지연된 동작 ( 키워드 사용 )
  • 가드 문 ( guard키워드 사용 )을 사용 하면 일반적인 오류 확인 / 신호 코드보다 if / else 코드를 적게 작성할 수 있습니다.

스위프트 1

런타임 오류 :

Leandros는 네트워크 연결 문제, 데이터 구문 분석, 파일 열기 등과 같은 런타임 오류 처리에 NSError대해 Foundation, AppKit, UIKit 등이 이러한 방식으로 오류를보고하므로 ObjC에서와 같이 사용해야합니다 . 언어보다 프레임 워크가 더 중요합니다.

AFNetworking과 같은 분리기 성공 / 실패 블록이 사용되는 또 다른 빈번한 패턴은 다음과 같습니다.

var sessionManager = AFHTTPSessionManager(baseURL: NSURL(string: "yavin4.yavin.planets"))
sessionManager.HEAD("/api/destoryDeathStar", parameters: xwingSquad,
    success: { (NSURLSessionDataTask) -> Void in
        println("Success")
    },
    failure:{ (NSURLSessionDataTask, NSError) -> Void in
        println("Failure")
    })

여전히 오류 블록 NSError은 오류를 설명하는 인스턴스를 자주 받았습니다 .

프로그래머 오류 :

프로그래머 오류 (배열 요소의 범위를 벗어난 액세스, 함수 호출에 전달 된 잘못된 인수 등)의 경우 ObjC에서 예외를 사용했습니다. Swift 언어는 예외 (예 throw: catch, 등의 키워드)에 대한 언어 지원이없는 것 같습니다 . 그러나 문서에 따르면 ObjC와 동일한 런타임에서 실행 중이므로 여전히 NSExceptions다음과 같이 던질 수 있습니다 .

NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()

ObjC 코드에서 예외를 포착하도록 선택할 수도 있지만 순수한 Swift에서는이를 포착 할 수 없습니다.

문제는 프로그래머 오류에 대한 예외를 던지거나 언어 가이드에서 Apple이 제안한 어설 션을 사용해야하는지 여부입니다.


답변

2015 년 6 월 9 일 업데이트-매우 중요

스위프트 2.0 함께 제공 try, throwcatch키워드와 가장 흥미로운입니다 :

Swift는 오류를 생성하는 Objective-C 메소드를 Swift의 기본 오류 처리 기능에 따라 오류를 발생시키는 메소드로 자동 변환합니다.

참고 : NSError 객체 인수와 함께 완료 처리기를 사용하는 대리자 메서드 또는 메서드와 같이 오류를 소비하는 메서드는 Swift에서 가져올 때 throw되는 메서드가 아닙니다.

발췌 : Apple Inc.“Cocoa 및 Objective-C와 함께 Swift 사용 (Swift 2 시험판).” iBooks.

예 : (책에서)

NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *URL = [NSURL fileURLWithPath:@"/path/to/file"];
NSError *error = nil;
BOOL success = [fileManager removeItemAtURL:URL error:&error];
if (!success && error){
    NSLog(@"Error: %@", error.domain);
}

신속한 대응은 다음과 같습니다.

let fileManager = NSFileManager.defaultManager()
let URL = NSURL.fileURLWithPath("path/to/file")
do {
    try fileManager.removeItemAtURL(URL)
} catch let error as NSError {
    print ("Error: \(error.domain)")
}

오류 발생 :

*errorPtr = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotOpenFile userInfo: nil]

발신자에게 자동으로 전파됩니다.

throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, userInfo: nil)

Apple의 책에서 Swift Programming Language는 enum을 사용하여 오류를 처리해야합니다.

다음은이 책의 예입니다.

enum ServerResponse {
    case Result(String, String)
    case Error(String)
}

let success = ServerResponse.Result("6:00 am", "8:09 pm")
let failure = ServerResponse.Error("Out of cheese.")

switch success {
case let .Result(sunrise, sunset):
    let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."
case let .Error(error):
    let serverResponse = "Failure...  \(error)"
}

출처 : Apple Inc. “Swift Programming Language” iBooks. https://itun.es/br/jEUH0.l

최신 정보

Apple 뉴스 서적, “Cocoa 및 Objective-C와 함께 Swift 사용”. 신속한 언어를 사용하면 런타임 예외가 발생하지 않으므로 try-catch가 없습니다. 대신 옵션 체인 을 사용 합니다.

이 책의 내용은 다음과 같습니다.

예를 들어 아래 코드 목록에서 length 속성과 characterAtIndex : 메서드가 NSDate 개체에 없기 때문에 첫 번째와 두 번째 줄은 실행되지 않습니다. myLength 상수는 선택적 Int 인 것으로 추론되며 nil로 설정됩니다. if-let 문을 사용하여 3 행에 표시된 것처럼 오브젝트가 응답하지 않을 수있는 메소드의 결과를 조건부 랩 해제 할 수도 있습니다.

let myLength = myObject.length?
let myChar = myObject.characterAtIndex?(5)
if let fifthCharacter = myObject.characterAtIndex(5) {
    println("Found \(fifthCharacter) at index 5")
}

발췌 : Apple Inc.“Cocoa 및 Objective-C와 함께 Swift 사용” iBooks. https://itun.es/br/1u3-0.l


또한이 책은 Objective-C (NSError Object)의 코코아 오류 패턴을 사용하도록 권장합니다.

Swift의 오류보고는 Objective-C에서와 동일한 패턴을 따르며 선택적 반환 값을 제공 할 수 있다는 이점이 있습니다. 가장 간단한 경우 함수에서 Bool 값을 반환하여 성공했는지 여부를 나타냅니다. 오류 이유를보고해야하는 경우 NSErrorPointer 유형의 NSError 출력 매개 변수를 함수에 추가 할 수 있습니다. 이 유형은 추가 메모리 안전 및 선택적 입력을 통해 Objective-C의 NSError **와 거의 동일합니다. 아래 코드 목록에 표시된 것처럼 접두사 및 연산자를 사용하여 선택적 NSError 유형에 대한 참조를 NSErrorPointer 객체로 전달할 수 있습니다.

var writeError : NSError?
let written = myString.writeToFile(path, atomically: false,
    encoding: NSUTF8StringEncoding,
    error: &writeError)
if !written {
    if let error = writeError {
        println("write failure: \(error.localizedDescription)")
    }
}

발췌 : Apple Inc.“Cocoa 및 Objective-C와 함께 Swift 사용” iBooks. https://itun.es/br/1u3-0.l


답변

Objective-C의 접근 방식과 비슷한 Swift에는 예외가 없습니다.

개발 과정에서 assert나타날 수있는 오류를 파악하고 프로덕션 환경으로 가기 전에 수정해야 할 때 사용할 수 있습니다.

고전적인 NSError접근 방식은 변경되지 않고을 보내면 NSErrorPointer채워집니다.

간단한 예 :

var error: NSError?
var contents = NSFileManager.defaultManager().contentsOfDirectoryAtPath("/Users/leandros", error: &error)
if let error = error {
    println("An error occurred \(error)")
} else {
    println("Contents: \(contents)")
}


답변

권장되는 ‘Swift Way’는 다음과 같습니다.

func write(path: String)(#error: NSErrorPointer) -> Bool { // Useful to curry error parameter for retrying (see below)!
    return "Hello!".writeToFile(path, atomically: false, encoding: NSUTF8StringEncoding, error: error)
}

var writeError: NSError?
let written = write("~/Error1")(error: &writeError)
if !written {
    println("write failure 1: \(writeError!.localizedDescription)")
    // assert(false) // Terminate program
}

그러나 오류 처리를 끝에 별도의 블록으로 이동하기 때문에 추적하기가 더 쉬우므로 try / catch를 선호합니다.이 배열을 “황금 경로”라고도합니다. 운이 좋으면 클로저 로이 작업을 수행 할 수 있습니다.

TryBool {
    write("~/Error2")(error: $0) // The code to try
}.catch {
    println("write failure 2: \($0!.localizedDescription)") // Report failure
    // assert(false) // Terminate program
}

또한 재시도 기능을 쉽게 추가 할 수 있습니다.

TryBool {
    write("~/Error3")(error: $0) // The code to try
}.retry {
    println("write failure 3 on try \($1 + 1): \($0!.localizedDescription)")
    return write("~/Error3r")  // The code to retry
}.catch {
    println("write failure 3 catch: \($0!.localizedDescription)") // Report failure
    // assert(false) // Terminate program
}

TryBool의 리스팅은 다음과 같습니다 :

class TryBool {
    typealias Tryee = NSErrorPointer -> Bool
    typealias Catchee = NSError? -> ()
    typealias Retryee = (NSError?, UInt) -> Tryee

    private var tryee: Tryee
    private var retries: UInt = 0
    private var retryee: Retryee?

    init(tryee: Tryee) {
        self.tryee = tryee
    }

    func retry(retries: UInt, retryee: Retryee) -> Self {
        self.retries = retries
        self.retryee = retryee
        return self
    }
    func retry(retryee: Retryee) -> Self {
        return self.retry(1, retryee)
    }
    func retry(retries: UInt) -> Self {
        // For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil
        self.retries = retries
        retryee = nil
        return self
    }
    func retry() -> Self {
        return retry(1)
    }

    func catch(catchee: Catchee) {
        var error: NSError?
        for numRetries in 0...retries { // First try is retry 0
            error = nil
            let result = tryee(&error)
            if result {
                return
            } else if numRetries != retries {
                if let r = retryee {
                    tryee = r(error, numRetries)
                }
            }
        }
        catchee(error)
    }
}

Bool 값 대신 Optional 반환 값을 테스트하기 위해 비슷한 클래스를 작성할 수 있습니다.

class TryOptional<T> {
    typealias Tryee = NSErrorPointer -> T?
    typealias Catchee = NSError? -> T
    typealias Retryee = (NSError?, UInt) -> Tryee

    private var tryee: Tryee
    private var retries: UInt = 0
    private var retryee: Retryee?

    init(tryee: Tryee) {
        self.tryee = tryee
    }

    func retry(retries: UInt, retryee: Retryee) -> Self {
        self.retries = retries
        self.retryee = retryee
        return self
    }
    func retry(retryee: Retryee) -> Self {
        return retry(1, retryee)
    }
    func retry(retries: UInt) -> Self {
        // For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil
        self.retries = retries
        retryee = nil
        return self
    }
    func retry() -> Self {
        return retry(1)
    }

    func catch(catchee: Catchee) -> T {
        var error: NSError?
        for numRetries in 0...retries {
            error = nil
            let result = tryee(&error)
            if let r = result {
                return r
            } else if numRetries != retries {
                if let r = retryee {
                    tryee = r(error, numRetries)
                }
            }
        }
        return catchee(error)
    }
}

TryOptional 버전은 선택적 프로그래밍이 아닌 리턴 유형을 적용하여 ‘Swift Way :

struct FailableInitializer {
    init?(_ id: Int, error: NSErrorPointer) {
        // Always fails in example
        if error != nil {
            error.memory = NSError(domain: "", code: id, userInfo: [:])
        }
        return nil
    }
    private init() {
        // Empty in example
    }
    static let fallback = FailableInitializer()
}

func failableInitializer(id: Int)(#error: NSErrorPointer) -> FailableInitializer? { // Curry for retry
    return FailableInitializer(id, error: error)
}

var failError: NSError?
var failure1Temp = failableInitializer(1)(error: &failError)
if failure1Temp == nil {
    println("failableInitializer failure code: \(failError!.code)")
    failure1Temp = FailableInitializer.fallback
}
let failure1 = failure1Temp! // Unwrap

TryOptional 사용하기 :

let failure2 = TryOptional {
    failableInitializer(2)(error: $0)
}.catch {
    println("failableInitializer failure code: \($0!.code)")
    return FailableInitializer.fallback
}

let failure3 = TryOptional {
    failableInitializer(3)(error: $0)
}.retry {
    println("failableInitializer failure, on try \($1 + 1), code: \($0!.code)")
    return failableInitializer(31)
}.catch {
    println("failableInitializer failure code: \($0!.code)")
    return FailableInitializer.fallback
}

자동 언 랩핑을 참고하십시오.


답변

편집 : 이 답변은 효과가 있지만 Objective-C가 Swift로 음역되는 것 이상입니다. Swift 2.0의 변경으로 인해 더 이상 사용되지 않습니다. 위의 Guilherme Torres Castro의 답변은 Swift에서 오류를 처리하는 기본 방법에 대한 아주 좋은 소개입니다. VOS

알아내는 데 약간의 시간이 걸렸지 만 나는 그것을 생각한 것 같습니다. 그래도 추한 것 같습니다. Objective-C 버전의 얇은 스킨에 지나지 않습니다.

NSError 매개 변수를 사용하여 함수 호출 …

var fooError : NSError ? = nil

let someObject = foo(aParam, error:&fooError)

// Check something was returned and look for an error if it wasn't.
if !someObject {
   if let error = fooError {
      // Handle error
      NSLog("This happened: \(error.localizedDescription)")
   }
} else {
   // Handle success
}`

에러 파라미터를받는 함수 작성 중 …

func foo(param:ParamObject, error: NSErrorPointer) -> SomeObject {

   // Do stuff...

   if somethingBadHasHappened {
      if error {
         error.memory = NSError(domain: domain, code: code, userInfo: [:])
      }
      return nil
   }

   // Do more stuff...
}


답변

시험 Catch 기능을 제공하는 objective C 주변의 기본 래퍼입니다.
https://github.com/williamFalcon/SwiftTryCatch

다음과 같이 사용하십시오.

SwiftTryCatch.try({ () -> Void in
        //try something
     }, catch: { (error) -> Void in
        //handle error
     }, finally: { () -> Void in
        //close resources
})


답변

이것은 swift 2.0에 대한 업데이트 답변입니다. Java와 같은 기능이 풍부한 오류 처리 모델을 기대하고 있습니다. 마침내 그들은 좋은 소식을 발표했습니다. 여기

오류 처리 모델 : Swift 2.0의 새로운 오류 처리 모델은 익숙한 try, throw 및 catch 키워드를 사용 하여 자연스럽게 느껴집니다 . 무엇보다도 Apple SDK 및 NSError와 완벽하게 작동하도록 설계되었습니다. 실제로 NSError는 Swift의 ErrorType을 따릅니다. Swift의 새로운 기능에 대한 WWDC 세션을 확실히보고 싶을 것입니다.

예 :

func loadData() throws { }
func test() {
do {
    try loadData()
} catch {
    print(error)
}}