Я не слишком много читал о Swift, но заметил одну вещь: исключений нет. Так как же они обрабатывают ошибки в Swift? Кто-нибудь нашел что-нибудь связанное с обработкой ошибок?
Я не слишком много читал о Swift, но заметил одну вещь: исключений нет. Так как же они обрабатывают ошибки в Swift? Кто-нибудь нашел что-нибудь связанное с обработкой ошибок?
Ответы:
Ситуация немного изменилась в Swift 2, поскольку появился новый механизм обработки ошибок, который несколько больше похож на исключения, но отличается в деталях.
Если функция / метод хочет указать, что она может выдать ошибку, она должна содержать throws
ключевое слово, подобное этому
func summonDefaultDragon() throws -> Dragon
Примечание: не существует спецификации для типа ошибки, которую функция может выдать. В этом объявлении просто говорится, что функция может генерировать экземпляр любого типа, реализующий ErrorType, или не генерирует вообще.
Для вызова функции вам нужно использовать ключевое слово 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
ключевым словом:
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()
Если функция фактически выдает ошибку, то вы получите ошибку времени выполнения в вашем приложении, и приложение прекратит работу.
Чтобы сгенерировать ошибку, используйте ключевое слово throw, как это
throw DragonError.dragonIsMissing
Вы можете бросить все, что соответствует ErrorType
протоколу. Для начала NSError
соответствует этому протоколу, но вы, вероятно, хотели бы использовать enum-based, ErrorType
который позволяет группировать несколько связанных ошибок, возможно, с дополнительными фрагментами данных, например
enum DragonError: ErrorType {
case dragonIsMissing
case notEnoughMana(requiredMana: Int)
...
}
Основные различия между новым механизмом ошибок Swift 2 и 3 и исключениями в стиле Java / C # / C ++ заключаются в следующем:
do-catch
+ try
+ defer
против традиционного try-catch-finally
синтаксиса.do-catch
блок не будет перехватывать исключение NSEx и наоборот, для этого вы должны использовать ObjC.NSError
соглашениями метода Какао о возвращении либо false
(для Bool
возврата функций), либо nil
(для AnyObject
возврата функций) и передачи NSErrorPointer
с подробностями ошибки.В качестве дополнительного синтетического сахара для облегчения обработки ошибок, есть еще две концепции
defer
ключевого слова), которые позволяют достичь того же эффекта, что и блоки finally в Java / C # / etcguard
ключевого слова), который позволяет писать немного меньше кода if / else, чем в обычном коде проверки / сигнализации ошибокОшибки во время выполнения:
Как 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
ключевое слово etc). Тем не менее, как видно из документации, он работает в той же среде выполнения, что и ObjC, и поэтому вы все еще можете выполнить NSExceptions
следующее:
NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()
Вы просто не можете поймать их в чистом Swift, хотя вы можете выбрать перехват исключений в коде ObjC.
Вопрос заключается в том, следует ли вам выдавать исключения для ошибок программиста, или, скорее, использовать утверждения, как Apple предлагает в руководстве по языку.
fatalError(...)
такая же , как хорошо.
Обновление 9 июня 2015 - Очень важно
Swift 2.0 поставляется с try
, throw
и catch
ключевыми словами и наиболее интересным является:
Swift автоматически переводит методы Objective-C, которые генерируют ошибки, в методы, которые выдают ошибку в соответствии с собственной функцией обработки ошибок Swift.
Примечание. Методы, которые используют ошибки, такие как методы делегатов или методы, которые принимают обработчик завершения с аргументом объекта NSError, не становятся методами, которые выдают при импорте Swift.
Отрывок из: Apple Inc. «Использование Swift с какао и Objective-C (пререлиз Swift 2)». интерактивные книги.
Пример: (из книги)
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, кажется, что ошибки должны обрабатываться с помощью 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». интерактивные книги. https://itun.es/br/jEUH0.l
Обновить
Из новостных книг Apple «Использование Swift с какао и Objective-C». Исключения во время выполнения не возникают при использовании быстрых языков, поэтому у вас нет 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 с какао и Objective-C». интерактивные книги. https://itun.es/br/1u3-0.l
И книги также побуждают вас использовать образец ошибки какао из Objective-C (NSError Object)
Отчеты об ошибках в Swift следуют той же схеме, что и в Objective-C, с дополнительным преимуществом предложения дополнительных возвращаемых значений. В простейшем случае вы возвращаете значение Bool из функции, чтобы указать, успешно ли оно выполнено. Когда вам нужно сообщить причину ошибки, вы можете добавить в функцию параметр NSError out типа NSErrorPointer. Этот тип примерно эквивалентен NSError ** в Objective-C, с дополнительной безопасностью памяти и необязательной типизацией. Вы можете использовать префикс & оператор для передачи ссылки на необязательный тип 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 с какао и Objective-C». интерактивные книги. https://itun.es/br/1u3-0.l
В 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)")
}
f();g();
становится f(&err);if(err) return;g(&err);if(err) return;
на первый месяц, потом просто становитсяf(nil);g(nil);hopeToGetHereAlive();
Рекомендуемый «быстрый путь»:
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
}
Однако я предпочитаю попробовать / поймать, так как мне легче следовать, потому что в конце он переносит обработку ошибок в отдельный блок, такое расположение иногда называют «Золотым путем». К счастью, вы можете сделать это с помощью замыканий:
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 применяется необязательный тип возвращаемого значения, что облегчает последующее программирование, например, «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. Ответ Гильерма Торреса Кастро, приведенный выше, является очень хорошим введением в предпочтительный способ обработки ошибок в 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...
}
Базовая оболочка вокруг цели 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
})
Это обновленный ответ для Swift 2.0. Я с нетерпением жду многофункциональную модель обработки ошибок, как в Java. Наконец, они объявили хорошие новости. Вот
Модель обработки ошибок: новая модель обработки ошибок в Swift 2.0 сразу почувствует себя естественной, со знакомыми ключевыми словами try, throw и catch . Лучше всего то, что он был разработан для идеальной работы с Apple SDK и NSError. На самом деле, NSError соответствует Swift ErrorType. Вы определенно захотите посмотреть сеанс WWDC «Что нового в Swift», чтобы узнать об этом больше.
например:
func loadData() throws { }
func test() {
do {
try loadData()
} catch {
print(error)
}}
Как сказал Гильерме Торрес Кастро, в Swift 2.0, try
, catch
, do
могут быть использованы при программировании.
Например, в CoreData выборки метод данных, вместо того , чтобы положить в &error
качестве параметра в managedContext.executeFetchRequest(fetchRequest, error: &error)
, теперь нужно использовать только использовать , managedContext.executeFetchRequest(fetchRequest)
а затем обработать ошибку с try
, catch
( Apple , Document Link )
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 28м30 с)
Обработка ошибок - новая функция Swift 2.0. Он использует try
, throw
и catch
ключевые слова.
См. Объявление Apple Swift 2.0 на официальном блоге Apple Swift.
Хорошая и простая библиотека для обработки исключений: TryCatchFinally-Swift
Как и некоторые другие, он оборачивается объективными функциями C-исключений.
Используйте это так:
try {
println(" try")
}.catch { e in
println(" catch")
}.finally {
println(" finally")
}
Начиная с 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.
Что я видел, так это то, что из-за природы устройства вы не хотите, чтобы пользователь получал кучу загадочных сообщений об обработке ошибок. Вот почему большинство функций возвращают необязательные значения, а вы просто код, чтобы игнорировать необязательные. Если функция возвращается nil, что означает, что она не удалась, вы можете выдать сообщение или что-то еще.