Я не надто багато читав у Swift, але одне, що я помітив, - це не винятки. То як вони роблять помилки в Swift? Хто-небудь знайшов щось, пов’язане з поводженням з помилками?
Я не надто багато читав у Swift, але одне, що я помітив, - це не винятки. То як вони роблять помилки в Swift? Хто-небудь знайшов щось, пов’язане з поводженням з помилками?
Відповіді:
У Swift 2 все трохи змінилося, оскільки з'явився новий механізм поводження з помилками, який трохи більше схожий на винятки, але відрізняється деталізацією.
Якщо функція / метод хоче вказати, що може призвести до помилки, вона повинна містити таке throws
ключове слово
func summonDefaultDragon() throws -> Dragon
Примітка. Немає специфікації для типу помилки, яку може насправді випустити функція. У цій декларації просто зазначено, що функція може кидати екземпляр будь-якого типу, що реалізує ErrorType або взагалі не кидає.
Для виклику функції потрібно скористатися ключовим словом спробувати, як це
try summonDefaultDragon()
цей рядок зазвичай повинен бути присутнім блоком приховання, як це
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
}
Примітка. У пункті лову використовуйте всі потужні функції відповідності шаблону Swift, тому ви тут дуже гнучкі.
Ви можете вирішити поширити помилку, якщо ви викликаєте функцію кидання з функції, яка сама позначена throws
ключовим словом:
func fulfill(quest: Quest) throws {
let dragon = try summonDefaultDragon()
quest.ride(dragon)
}
Крім того, ви можете викликати функцію метання за допомогою try?
:
let dragonOrNil = try? summonDefaultDragon()
Таким чином ви отримуєте повернене значення або нуль, якщо сталася якась помилка. Використовуючи цей спосіб, ви не отримуєте об'єкт помилки.
Що означає, що ви також можете поєднувати try?
з корисними твердженнями, такими як:
if let dragon = try? summonDefaultDragon()
або
guard let dragon = try? summonDefaultDragon() else { ... }
Нарешті, ви можете вирішити, що знаєте, що помилка насправді не відбудеться (наприклад, тому, що ви вже перевірили, є необхідними умовами), і використовувати try!
ключове слово:
let dragon = try! summonDefaultDragon()
Якщо функція насправді видаляє помилку, ви отримаєте помилку виконання у вашій програмі, і програма припиниться.
Для того, щоб викинути помилку, ви використовуєте таке ключове слово
throw DragonError.dragonIsMissing
Ви можете кинути все, що відповідає ErrorType
протоколу. Для початківців NSError
відповідає цей протокол, але ви, ймовірно, хотіли б перейти на enum-основі, 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
блок не буде вловлювати жодну NSException, і навпаки, для цього ви повинні використовувати ObjC.NSError
методу какао щодо повернення false
(для Bool
повернення функцій) або nil
(для AnyObject
повернення функцій) та передачі NSErrorPointer
з деталями помилок.Як додатковий синтатичний цукор для полегшення поводження з помилками, є ще два поняття
defer
ключового слова), які дозволяють досягти такого ж ефекту, що і, нарешті, блоки в Java / C # / тощоguard
ключового слова), яка дозволяє писати трохи менше коду if / else, ніж у звичайній коді перевірки помилок / сигналізації.Помилки під час виконання:
Як Леандрос пропонує для обробки помилок виконання (наприклад, проблеми з підключенням до мережі, розбору даних, відкриття файлу тощо), вам слід користуватися так, 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 у мовному посібнику.
fatalError(...)
ж саме.
Оновлення 9 червня 2015 р. - Дуже важливо
Swift 2.0 поставляється з try
, throw
і catch
ключовими словами і найцікавішим є:
Swift автоматично переводить методи Objective-C, які створюють помилки, у методи, які видаляють помилку відповідно до функцій обробки помилок Swift.
Примітка. Методи, які споживають помилки, такі як методи делегування або методи, які приймають обробник завершення з аргументом об'єкта NSError, не стають методами, які кидаються при імпорті Swift.
Уривок: Apple Inc. "Використання Swift з какао та Objective-C (Swift 2 Prerelease)". 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, The 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. "Мова швидкого програмування". iBooks. https://itun.es/br/jEUH0.l
Оновлення
З новинних книг Apple "Використання Swift з какао та Objective-C". Винятки з виконання не відбуваються за допомогою швидких мов, тому для вас немає спроб лову. Замість цього ви використовуєте необов'язкове ланцюжок .
Ось розтягнення з книги:
Наприклад, у списку кодів нижче перший та другий рядки не виконуються, оскільки властивість length та метод characterAtIndex: не існують на об’єкті NSDate. Константа myLength вважається необов'язковою Int і встановлюється на нуль. Ви також можете використовувати оператор 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". iBooks. 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". iBooks. 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)
}
}
Версія TryOtional застосовує необов'язковий тип повернення, що спрощує подальше програмування, наприклад, "Швидкий шлях:
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
Використання TryOtional:
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
}
Зверніть увагу на автоматичне розгортання.
Редагувати: Хоча ця відповідь спрацьовує, це трохи більше, ніж об’єктив-C, транслітерований у Swift. Він був застарілий змінами в Swift 2.0. Відповідь Гільгерма Торреса Кастро - це дуже вдале введення у бажаний спосіб поводження з помилками у 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...
}
Основна обгортка навколо об'єкта C, що дає вам можливість спробувати улов. https://github.com/williamFalcon/SwiftTryCatch
Використовуйте як:
SwiftTryCatch.try({ () -> Void in
//try something
}, catch: { (error) -> Void in
//handle error
}, finally: { () -> Void in
//close resources
})
Це відповідь на оновлення для швидкого 2.0. Я з нетерпінням чекаю багатофункціональної моделі обробки помилок, як у Java. Нарешті вони оголосили добру новину. тут
Модель обробки помилок: Нова модель поводження з помилками у Swift 2.0 миттєво відчує себе природно, із знайомими ключовими словами пробувати, кидати та ловити . Найкраще, що він був розроблений для того, щоб ідеально співпрацювати з Apple SDK та NSError. Насправді, NSError відповідає типу помилки Swift. Ви обов'язково захочете переглянути сесію 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. Спробуйте пошукати помилки в кидці в Documentations і API Reference та виберіть перший показ результату, він дає основне уявлення, що можна зробити для цього нового синтаксису. Однак повністю документація ще не розміщена для багатьох API.
Більше фантазійних методів обробки помилок можна знайти в
Що нового у Swift (сесія 2015 року, 28 2830)
Помилка обробки - нова функція 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
}
})
Це здається дещо чистішим, ніж окремий зворотний виклик помилки Handler переданий асинхронній функції, як це було б оброблено до Swift 2.
Що я бачив, це те, що з-за характеру пристрою ви не хочете, щоб викидали користувачеві повідомлення з критичними помилками. Ось чому більшість функцій повертає необов’язкові значення, тоді ви просто кодуєте, щоб ігнорувати необов'язкові. Якщо функція повертається до нуля, це означає, що вона не вдалася, ви можете надіслати повідомлення чи що завгодно.