Обработка ошибок в Swift-Language

179

Я не слишком много читал в Swift, но я заметил, что исключений нет. Итак, как они обрабатывают ошибки в Swift? Кто-нибудь нашел что-нибудь, связанное с обработкой ошибок?

  • 1
    Я нашел сообщения об ошибках, как и в Obj-C: o
  • 13
    @ Арбитур старый добрый путь Сегфо?
Показать ещё 3 комментария
Теги:
error-handling

12 ответов

129
Лучший ответ

Swift 2 & 3

В Swift 2 ситуация немного изменилась, так как появился новый механизм обработки ошибок, который несколько больше похож на исключения, но отличается в деталях.

1. Индикация ошибки

Если функция/метод хочет указать, что она может выдать ошибку, она должна содержать ключевое слово throws, подобное этому

func summonDefaultDragon() throws -> Dragon

Примечание. Спецификация типа ошибки, которую может фактически выполнить функция, не существует. В этом заявлении просто указывается, что функция может вызывать экземпляр любого типа, реализующего ErrorType или вообще не метать.

2. Вызов функции, которая может вызывать ошибки

Чтобы вызвать функцию, вам нужно использовать ключевое слово try, например

try summonDefaultDragon()

В этой строке обычно должен присутствовать блок catch-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:

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

В качестве альтернативы вы можете вызвать функцию throwing с помощью 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 this this

throw DragonError.dragonIsMissing

Вы можете выбросить все, что соответствует протоколу ErrorType. Для начала NSError соответствует этому протоколу, но вы, вероятно, хотели бы перейти с основанной на enum ErrorType, которая позволяет группировать множественные связанные ошибки, потенциально с дополнительными фрагментами данных, такими как

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

Основные отличия между новыми механизмами ошибок Swift 2 и 3 и исключениями стиля Java/С#/С++:

  • Синтаксис немного отличается: do-catch + try + defer против традиционного синтаксиса try-catch-finally.
  • Обработка исключений обычно приводит к значительному времени выполнения в пути исключения, чем в пути успеха. Это не относится к ошибкам Swift 2.0, где путь успеха и путь ошибки примерно одинаковы.
  • Все коды метаданных должны быть объявлены, в то время как исключения могут быть выброшены из любого места. Все ошибки являются "проверенными исключениями" в номенклатуре Java. Однако, в отличие от Java, вы не указываете потенциально заброшенные ошибки.
  • Быстрые исключения несовместимы с исключениями ObjC. Ваш блок do-catch не поймает никакого NSException, и наоборот, для этого вы должны использовать ObjC.
  • Исключения Swift совместимы с условными соглашениями Cocoa NSError для возврата либо false (для Bool возвращающих функций), либо nil (для AnyObject возвращающих функций) и передачи NSErrorPointer с данными об ошибках.

В качестве дополнительного синтаксического сахара для облегчения обработки ошибок существуют еще две концепции

  • отложенные действия (с использованием ключевого слова defer), которые позволяют вам добиться того же эффекта, что и окончательные блоки в Java/С#/etc
  • (с использованием ключевого слова guard), который позволяет вам писать немного меньше кода if/else, чем в нормальном коде проверки ошибок/сигнализации.

Swift 1

Ошибки времени выполнения:

Как рекомендует Leandros для обработки ошибок времени выполнения (например, проблем с подключением к сети, анализа данных, открытия файла и т.д.), вы должны использовать NSError, как и в ObjC, потому что Foundation, AppKit, UIKit и т.д. сообщают об ошибках в этом путь. Так что это больше рамки, чем язык.

Другим частым шаблоном, который используется, являются блоки успеха/отказа сепаратора, например, в 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()

Вы просто не можете поймать их в чистом Swift, хотя вы можете выбрать исключения в коде ObjC.

Вопрос заключается в том, следует ли исключать исключения для ошибок программиста или, скорее, использовать утверждения, которые Apple предлагает в руководстве по языку.

  • 19
    «проблемы с сетевым подключением» и «открытие файлов» с помощью API-интерфейсов Какао (NSFileHandle) могут вызывать исключения, которые необходимо перехватить. Без исключений в Swift, вам нужно реализовать эту часть вашей программы в Objective-C или выполнить всю вашу работу, используя API-интерфейсы BSD C (оба из которых являются плохими обходными путями). См. Документацию для NSFileHandle.writeData для получения дополнительной информации ... developer.apple.com/library/ios/documentation/Cocoa/Reference/… :
  • 5
    Опять же, отсутствие обработки исключений означает двухэтапное построение объекта со всеми присущими ему проблемами. См. Stroustrup.com/except.pdf .
Показать ещё 5 комментариев
65

Обновление 9 июня 2015 г. - очень важно

Swift 2.0 поставляется с ключевыми словами try, throw и catch, и наиболее интересным является:

Swift автоматически переводит методы Objective-C, которые вызывают ошибки в методах, которые вызывают ошибку в соответствии с функциональными возможностями обработки ошибок Swift.

Примечание. Методы, которые используют ошибки, такие как методы или методы делегата которые принимают обработчик завершения с аргументом объекта NSError, не становятся методами, которые бросаются при импорте Swift.

Отрывок из: Apple Inc. "Использование Swift с Cocoa и Objective-C (Swift 2 Preerelease)." интерактивные книги.суб >

Пример: (из книги)

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

Эквивалент в swift будет:

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 Язык программирования, кажется, ошибки должны обрабатываться с использованием перечисления.

Вот пример из книги.

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. "Быстрый язык программирования". интерактивные книги. https://itun.es/br/jEUH0.l

Обновление

Из новостных книг Apple, "Использование Swift с Cocoa и Objective-C". Исключения в Runtime не происходят с использованием быстрых языков, поэтому почему у вас нет try-catch. Вместо этого вы используете Необязательный цепочек.

Вот пример из книги:

Например, в приведенном ниже списке кода первая и вторая строки не выполняется, поскольку свойство length и characterAtIndex: метод не существует на объекте NSDate. Константа myLength является предполагается, что он является необязательным Int и установлен в nil. Вы также можете использовать if-let, чтобы условно развернуть результат метода, который объект может не отвечать, как показано в строке три

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

Отрывок из: Apple Inc. "Использование Swift с Cocoa и Objective-C." интерактивные книги. https://itun.es/br/1u3-0.l


И книги также рекомендуют использовать шаблон ошибки Cocoa из Objective-C (объект NSError)

Отчет об ошибках в Swift следует тому же шаблону, который он делает в Objective-C, с дополнительным преимуществом предоставления дополнительного возврата значения. В простейшем случае вы возвращаете значение Bool из чтобы указать, удалось ли это сделать. Когда вам нужно сообщите причину ошибки, вы можете добавить к функции an Параметр NSError out типа NSErrorPointer. Этот тип примерно эквивалентно Objective-C s 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. "Использование Swift с Cocoa и Objective-C." интерактивные книги. https://itun.es/br/1u3-0.l

  • 0
    Для последнего оператора это должно быть: do {try myString.writeToFile (path, atomically: true, encoding: NSUTF8StringEncoding)} перехватить ошибку let как NSError {print (error)}
  • 1
    @Jacky Да, это верно для swift 2.0, хотя этот ответ был написан до выпуска swift 2.0, я обновил ответ, чтобы показать новый способ обработки ошибок в swift 2.0. Я подумал, позволив этот путь для справки, но я рассмотрю обновить весь ответ, чтобы использовать только Swift 2.0
13

В Swift нет исключений, аналогичных Objective-C.

В разработке вы можете использовать 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)")
}
  • 6
    Возникают два вопроса: что происходит, когда код ObjC, который мы вызываем из Swift, на самом деле выдает исключение, и является ли NSError нашим универсальным объектом ошибок, как в ObjC?
  • 0
    Я должен проверить первый позже, но для второго / Да, это так.
Показать ещё 10 комментариев
10

Рекомендуемый "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:

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 применяется нестандартный тип возврата, который упрощает последующее программирование, например. "Быстрый путь:

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
}

Обратите внимание на автоматическое разворачивание.

7

Изменить: Хотя этот ответ работает, он немного больше, чем Objective-C транслитерируется в Swift. Он был устаревшим благодаря изменениям в Swift 2.0. Ответ Guilherme Torres Castro выше, это очень хорошее введение в предпочтительный способ обработки ошибок в Swift. ВОС

Потребовалось немного разобраться, но я думаю, что я это обсудил. Это кажется уродливым. Не более чем тонкая кожа над версией 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...
}
4

Основная обертка вокруг объекта C, которая дает вам функцию try catch. https://github.com/williamFalcon/SwiftTryCatch

Используйте как:

SwiftTryCatch.try({ () -> Void in
        //try something
     }, catch: { (error) -> Void in
        //handle error
     }, finally: { () -> Void in
        //close resources
})
  • 0
    Отличная идея. Но кто решает использовать это, должен помнить, что объекты, выделенные в блоке try, не освобождаются при возникновении исключения. Это может вызвать проблемы объектов-зомби, и любое использование RAII скомпрометировано (авто-разблокировка, авто-sql-commit, авто-sql-rollback ...). Может быть, C ++ может помочь нам с какой-то формой «runAtExit»?
  • 0
    Обновление: я только что обнаружил, что в clang есть флаг, позволяющий освобождать объекты при выдаче исключений: -fobjc-arc-exceptions. Я должен попробовать, если это все еще работает с упакованной версией (я думаю, что это должно)
Показать ещё 2 комментария
3

Как сказал Гильерме Торрес Кастро, в Swift 2.0 в программировании могут использоваться try, catch, do.

Например, в методе ввода данных CoreData вместо put &error в качестве параметра в managedContext.executeFetchRequest(fetchRequest, error: &error), теперь нам нужно использовать use managedContext.executeFetchRequest(fetchRequest), а затем обработать ошибку с помощью try, catch (Ссылка на документ Apple)

do {
   let fetchedResults = try managedContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]
   if let results = fetchedResults{
      people = results
   }
} catch {
   print("Could not fetch")
}

Если вы уже загрузили бета-версию xcode7. Попробуйте выполнить поиск ошибок в документах и ​​справочнике API и выберите первый отображаемый результат, он дает базовую идею о том, что можно сделать для этого нового синтаксиса. Тем не менее, полная документация не является почтой для многих API.

Более причудливые методы обработки ошибок можно найти в

Что нового в Swift (2015 сессия 106 28m30s)

3

Это ответ на обновление для swift 2.0. Я с нетерпением жду многофункциональной модели обработки ошибок, например, в java. Наконец, они объявили хорошие новости. здесь

Модель обработки ошибок: новая модель обработки ошибок в Swift 2.0 будет мгновенно чувствую себя естественным, со знакомыми попытками, метать и ловить ключевые слова. Лучше всего, он был разработан, чтобы отлично работать с Apple SDK и NSError. Фактически, NSError соответствует Swifts ErrorType. Вы будете определенно хочу посмотреть сессию WWDC на Whats New в Swift до узнайте больше об этом.

например:

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

Обработка ошибок - это новая функция Swift 2.0. Он использует ключевые слова try, throw и catch.

Смотрите объявление Apple Swift 2.0 в официальном блоге Apple Swift

1

Начиная с Swift 2, как уже упоминалось ранее, обработка ошибок лучше всего достигается за счет использования перечислений do/try/catch и ErrorType. Это хорошо работает для синхронных методов, но для асинхронной обработки ошибок требуется немного умения.

В этой статье есть большой подход к этой проблеме:

https://jeremywsherman.com/blog/2015/06/17/using-swift-throws-with-completion-callbacks/

Подводя итог:

// create a typealias used in completion blocks, for cleaner code
typealias LoadDataResult = () throws -> NSData

// notice the reference to the typealias in the completionHandler
func loadData(someID: String, completionHandler: LoadDataResult -> Void)
    {
    completionHandler()
    }

тогда вызов к указанному выше методу будет следующим:

self.loadData("someString",
    completionHandler:     
        { result: LoadDataResult in
        do
            {
            let data = try result()
            // success - go ahead and work with the data
            }
        catch
            {
            // failure - look at the error code and handle accordingly
            }
        })

Это кажется немного чище, чем отдельный обратный вызов errorHandler, переданный асинхронной функции, а именно, как это будет обрабатываться до Swift 2.

0

Что я видел, так это то, что из-за природы устройства вы не хотите бросать кучу криптовальных сообщений об ошибках пользователя. Вот почему большинство функций возвращают необязательные значения, тогда вы просто код, чтобы игнорировать необязательный. Если функция возвращается, значит, это не сработало, вы можете отправить сообщение или что-то еще.

  • 1
    Возврат nil не возвращает информации о природе ошибки. Если объект ошибки возвращается при возникновении ошибки, то, в зависимости от ошибки, программист может игнорировать ее, обрабатывать, разрешать всплывать или «выдавать сообщение или что-то еще». Знание - сила.
0

Хорошая и простая lib для обработки исключений: TryCatchFinally-Swift

Как и некоторые другие, он обтекает объектные объекты C.

Используйте его следующим образом:

try {
    println("  try")
}.catch { e in
    println("  catch")
}.finally {
    println("  finally")
}
  • 0
    Я добавил образец :)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню