Nie czytałem zbyt wiele w Swift, ale zauważyłem, że nie ma wyjątków. Jak więc radzą sobie z obsługą błędów w Swift? Czy ktoś znalazł coś związanego z obsługą błędów?
Nie czytałem zbyt wiele w Swift, ale zauważyłem, że nie ma wyjątków. Jak więc radzą sobie z obsługą błędów w Swift? Czy ktoś znalazł coś związanego z obsługą błędów?
Odpowiedzi:
W Swift 2 wszystko się nieco zmieniło, ponieważ wprowadzono nowy mechanizm obsługi błędów, który jest nieco bardziej podobny do wyjątków, ale różni się szczegółowo.
Jeśli funkcja / metoda chce wskazać, że może zgłosić błąd, powinna zawierać throws
takie słowo kluczowe
func summonDefaultDragon() throws -> Dragon
Uwaga: nie ma specyfikacji rodzaju błędu, który funkcja może faktycznie wyrzucić. Ta deklaracja po prostu stwierdza, że funkcja może wyrzucić instancję dowolnego typu implementującą ErrorType lub w ogóle nie rzuca.
Aby wywołać funkcję, musisz użyć słowa kluczowego try, takiego jak ten
try summonDefaultDragon()
ta linia powinna normalnie występować w bloku catch-catch w ten sposób
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
}
Uwaga: klauzula catch wykorzystuje wszystkie zaawansowane funkcje dopasowania wzoru Swift, dzięki czemu jesteś bardzo elastyczny.
Możesz zdecydować się na propagowanie błędu, jeśli wywołujesz funkcję rzucania z funkcji, która sama jest oznaczona throws
słowem kluczowym:
func fulfill(quest: Quest) throws {
let dragon = try summonDefaultDragon()
quest.ride(dragon)
}
Alternatywnie możesz wywołać funkcję rzucania za pomocą try?
:
let dragonOrNil = try? summonDefaultDragon()
W ten sposób otrzymasz albo wartość zwracaną, albo zero, jeśli wystąpi jakikolwiek błąd. W ten sposób nie otrzymujesz obiektu błędu.
Co oznacza, że możesz także łączyć się try?
z przydatnymi stwierdzeniami, takimi jak:
if let dragon = try? summonDefaultDragon()
lub
guard let dragon = try? summonDefaultDragon() else { ... }
Na koniec możesz zdecydować, że wiesz, że błąd faktycznie nie wystąpi (np. Ponieważ już sprawdziłeś, czy są wymagane) i użyć try!
słowa kluczowego:
let dragon = try! summonDefaultDragon()
Jeśli funkcja faktycznie zgłasza błąd, wówczas w aplikacji pojawi się błąd czasu wykonywania, a aplikacja zostanie zakończona.
Aby zgłosić błąd, użyj słowa kluczowego rzutu takiego jak ten
throw DragonError.dragonIsMissing
Możesz rzucać wszystko, co jest zgodne z ErrorType
protokołem. Na początek NSError
jest zgodny z tym protokołem, ale prawdopodobnie chciałbyś skorzystać z metody wyliczania, ErrorType
która umożliwia grupowanie wielu powiązanych błędów, potencjalnie z dodatkowymi częściami danych, takimi jak ten
enum DragonError: ErrorType {
case dragonIsMissing
case notEnoughMana(requiredMana: Int)
...
}
Główne różnice między nowym mechanizmem błędów Swift 2 i 3 a wyjątkami w stylu Java / C # / C ++ są następujące:
do-catch
+ try
+ defer
vs. tradycyjnytry-catch-finally
składnią .do-catch
blok nie złapie żadnego wyjątku NSEx i vice versa, do tego musisz użyć ObjC.NSError
konwencjami metod Cocoa dotyczącymi zwracania albo false
(dla Bool
zwracanych funkcji) albo nil
(dla AnyObject
zwracanych funkcji) i przekazywania NSErrorPointer
ze szczegółami błędu.Jako dodatkowy cukier syntetyczny ułatwiający obsługę błędów, istnieją jeszcze dwie koncepcje
defer
słowa kluczowego), które pozwalają osiągnąć ten sam efekt, co ostatecznie bloki w Javie / C # / itpguard
słowa kluczowego), która pozwala napisać niewiele mniej kodu if / else niż w normalnym kodzie sprawdzania / sygnalizacji błędówBłędy czasu wykonania:
Jak sugeruje Leandros do obsługi błędów środowiska wykonawczego (takich jak problemy z połączeniem sieciowym, analizowanie danych, otwieranie pliku itp.), Powinieneś używać NSError
tak jak w ObjC, ponieważ Foundation, AppKit, UIKit itp. Zgłaszają swoje błędy w ten sposób. Więc jest to bardziej kwestia frameworka niż języka.
Innym częstym wzorcem, który jest używany, są bloki sukcesu / awarii separatora, takie jak w 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")
})
Nadal często otrzymywana jest blokada awarii NSError
opisująca błąd.
Błędy programatora:
W przypadku błędów programisty (takich jak przekroczenie granic elementu tablicy, niepoprawne argumenty przekazane do wywołania funkcji itp.) Zastosowano wyjątki w ObjC. Język Swift nie wydaje się mieć żadnego wsparcia językowego dla wyjątkami (jak throw
, catch
itp słów kluczowych). Jednak, jak sugeruje dokumentacja, działa on w tym samym środowisku uruchomieniowym co ObjC, dlatego nadal możesz rzucać w NSExceptions
ten sposób:
NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()
Po prostu nie możesz ich złapać w czystym Swift, chociaż możesz zdecydować się na łapanie wyjątków w kodzie ObjC.
Pytanie brzmi, czy należy zgłaszać wyjątki dla błędów programisty, czy raczej stosować twierdzenia, jak sugeruje Apple w przewodniku językowym.
fatalError(...)
jest taka sama, jak również.
Aktualizacja 9 czerwca 2015 r. - Bardzo ważne
Swift 2.0 wyposażony try
, throw
oraz catch
słów kluczowych i najbardziej ekscytujące jest:
Swift automatycznie tłumaczy metody Objective-C, które powodują błędy, na metody zgłaszające błąd zgodnie z natywną funkcją obsługi błędów Swift.
Uwaga: Metody, które zużywają błędy, takie jak metody delegowania lub metody, które przyjmują procedurę obsługi zakończenia z argumentem obiektu NSError, nie stają się metodami rzucającymi się po zaimportowaniu przez Swift.
Fragment: Apple Inc. „Używanie Swift z kakao i Objective-C (wstępna wersja Swift 2)”. iBooks.
Przykład: (z książki)
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);
}
Odpowiednikiem w swift będzie:
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)")
}
Zgłaszanie błędu:
*errorPtr = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotOpenFile userInfo: nil]
Zostaną automatycznie propagowane do dzwoniącego:
throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, userInfo: nil)
Z książek Apple, The Swift Programming Language, wydaje się, że błędy powinny być rozwiązywane za pomocą enum.
Oto przykład z książki.
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)"
}
Od: Apple Inc. „Swift Programming Language”. iBooks. https://itun.es/br/jEUH0.l
Aktualizacja
Z książek prasowych Apple „Używanie Swift z kakao i Objective-C”. Wyjątki czasu wykonywania nie występują przy użyciu szybkich języków, dlatego nie masz opcji catch-catch. Zamiast tego używasz opcjonalnego łączenia .
Oto fragment książki:
Na przykład w poniższym wykazie kodu pierwszy i drugi wiersz nie są wykonywane, ponieważ właściwość length i metoda characterAtIndex: nie istnieją w obiekcie NSDate. Stała myLength jest wywnioskowana jako opcjonalna wartość Int i jest ustawiona na zero. Możesz także użyć instrukcji if – let, aby warunkowo rozpakować wynik metody, na którą obiekt może nie odpowiedzieć, jak pokazano w trzecim wierszu
let myLength = myObject.length?
let myChar = myObject.characterAtIndex?(5)
if let fifthCharacter = myObject.characterAtIndex(5) {
println("Found \(fifthCharacter) at index 5")
}
Fragment: Apple Inc. „Używanie Swift z kakao i Objective-C”. iBooks. https://itun.es/br/1u3-0.l
Książki zachęcają również do korzystania ze wzoru błędu kakao z Objective-C (NSError Object)
Raportowanie błędów w Swift odbywa się według tego samego wzoru, co w Celu C, z dodatkową korzyścią oferowania opcjonalnych wartości zwrotu. W najprostszym przypadku zwraca się wartość Bool z funkcji, aby wskazać, czy się udało. Gdy musisz zgłosić przyczynę błędu, możesz dodać do funkcji parametr wyjścia NSError typu NSErrorPointer. Ten typ jest w przybliżeniu równoważny NSErrorowi Objective-C **, z dodatkowym bezpieczeństwem pamięci i opcjonalnym pisaniem. Przedrostka i operatora można użyć do przekazania odwołania do opcjonalnego typu NSError jako obiektu NSErrorPointer, jak pokazano na poniższej liście kodów.
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)")
}
}
Fragment: Apple Inc. „Używanie Swift z kakao i Objective-C”. iBooks. https://itun.es/br/1u3-0.l
W Swift nie ma wyjątków, podobnych do podejścia Celu C.
W fazie projektowania można użyć assert
do wychwycenia wszelkich błędów, które mogą się pojawić, i należy je naprawić przed przejściem do produkcji.
Klasyczne NSError
podejście się nie zmienia, wysyłasz wiadomość NSErrorPointer
, która zostaje zapełniona.
Krótki przykład:
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();
staje się f(&err);if(err) return;g(&err);if(err) return;
przez pierwszy miesiąc, a potem staje sięf(nil);g(nil);hopeToGetHereAlive();
Zalecany „Swift Way” to:
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
}
Jednak wolę try / catch, ponieważ łatwiej jest mi naśladować, ponieważ przesuwa on obsługę błędów do osobnego bloku na końcu, takie ustawienie jest czasem nazywane „Złotą Ścieżką”. Na szczęście możesz to zrobić za pomocą zamknięć:
TryBool {
write("~/Error2")(error: $0) // The code to try
}.catch {
println("write failure 2: \($0!.localizedDescription)") // Report failure
// assert(false) // Terminate program
}
Łatwo jest również dodać funkcję ponawiania:
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
}
Lista dla TryBool to:
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)
}
}
Możesz napisać podobną klasę do testowania opcjonalnej wartości zwracanej zamiast wartości 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)
}
}
Wersja TryOptional wymusza nie opcjonalny typ zwrotu, który ułatwia późniejsze programowanie, np. „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
Korzystanie z 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
}
Uwaga automatyczne rozpakowywanie.
Edycja: Chociaż ta odpowiedź działa, jest to niewiele więcej niż Cel-C transliterowany na Swift. Został on przestarzały przez zmiany w Swift 2.0. Powyższa odpowiedź Guilherme Torres Castro stanowi bardzo dobre wprowadzenie do preferowanego sposobu obsługi błędów w Swift. VOS
Trochę to rozgryzłem, ale myślę, że to zrobiłem. Wydaje się to brzydkie. Nic więcej niż cienka skóra nad wersją Objective-C.
Wywoływanie funkcji z parametrem 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
}`
Zapisywanie funkcji, która przyjmuje parametr błędu ...
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...
}
Podstawowe opakowanie wokół celu C, które daje funkcję try catch. https://github.com/williamFalcon/SwiftTryCatch
Użyj jak:
SwiftTryCatch.try({ () -> Void in
//try something
}, catch: { (error) -> Void in
//handle error
}, finally: { () -> Void in
//close resources
})
To jest odpowiedź na aktualizację dla Swift 2.0. Nie mogę się doczekać bogatego w funkcje modelu obsługi błędów jak w Javie. W końcu ogłosili dobre wieści. tutaj
Model obsługi błędów: nowy model obsługi błędów w Swift 2.0 będzie od razu wydawał się naturalny, ze znanymi słowami kluczowymi „spróbuj, rzucaj i łap” . Najlepsze jest to, że został zaprojektowany do współpracy z zestawami Apple SDK i NSError. W rzeczywistości NSError jest zgodny z typem błędu Swift. Na pewno będziesz chciał obejrzeć sesję WWDC w sekcji Co nowego w Swift, aby dowiedzieć się więcej na ten temat.
np .:
func loadData() throws { }
func test() {
do {
try loadData()
} catch {
print(error)
}}
Jak powiedział Guilherme Torres Castro, w Swift 2.0, try
, catch
,do
mogą być wykorzystywane w programowaniu.
Na przykład, w CoreData metodę pobierania danych, zamiast umieścić &error
jako parametr do managedContext.executeFetchRequest(fetchRequest, error: &error)
, teraz musimy tylko chcesz używać managedContext.executeFetchRequest(fetchRequest)
, a następnie z obsługi błędu try
, catch
( jabłko Document Link )
do {
let fetchedResults = try managedContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]
if let results = fetchedResults{
people = results
}
} catch {
print("Could not fetch")
}
Jeśli już pobrałeś xcode7 Beta. Spróbuj wyszukać błędy związane z rzucaniem w dokumentacji i dokumentacji API i wybierz pierwszy pokazujący wynik, daje to podstawowe wyobrażenie o tym, co można zrobić dla tej nowej składni. Jednak pełna dokumentacja nie jest jeszcze opublikowana dla wielu interfejsów API.
Bardziej wymyślne techniki obsługi błędów można znaleźć w
Co nowego w Swift (sesja 2015 2015 28 2830)
Obsługa błędów to nowa funkcja Swift 2.0. Wykorzystuje on try
, throw
i catch
słowa kluczowe.
Zobacz ogłoszenie Apple Swift 2.0 na oficjalnym blogu Apple Swift
Ładna i prosta biblioteka do obsługi wyjątku: TryCatchFinally-Swift
Podobnie jak kilka innych, obejmuje on funkcje celu wyjątku C.
Użyj tego w ten sposób:
try {
println(" try")
}.catch { e in
println(" catch")
}.finally {
println(" finally")
}
Począwszy od Swift 2, jak już wspomnieli inni, obsługa błędów najlepiej realizować za pomocą wyliczeń do / try / catch i ErrorType. Działa to całkiem dobrze w przypadku metod synchronicznych, ale do obsługi błędów asynchronicznych wymagana jest spryt.
Ten artykuł ma świetne podejście do tego problemu:
https://jeremywsherman.com/blog/2015/06/17/using-swift-throws-with-completion-callbacks/
Podsumowując:
// 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()
}
wówczas wywołanie powyższej metody wyglądałoby następująco:
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
}
})
Wydaje się to nieco czystsze niż oddzielne wywołanie zwrotne errorHandler przekazane do funkcji asynchronicznej, tak było to obsługiwane przed wersją Swift 2.
Widziałem, że ze względu na naturę urządzenia nie chcesz rzucać użytkownikowi wielu tajemniczych komunikatów o błędach. Dlatego większość funkcji zwraca wartości opcjonalne, po prostu kodujesz, aby zignorować opcjonalne. Jeśli funkcja wróci zero, co oznacza, że się nie powiodła, możesz wstawić wiadomość lub cokolwiek innego.