Non ho letto troppo in Swift ma una cosa che ho notato è che non ci sono eccezioni. Quindi, come fanno a gestire gli errori in Swift? Qualcuno ha trovato qualcosa legato alla gestione degli errori?
Non ho letto troppo in Swift ma una cosa che ho notato è che non ci sono eccezioni. Quindi, come fanno a gestire gli errori in Swift? Qualcuno ha trovato qualcosa legato alla gestione degli errori?
Risposte:
Le cose sono cambiate un po 'in Swift 2, poiché esiste un nuovo meccanismo di gestione degli errori, che è in qualche modo più simile alle eccezioni ma diverso nei dettagli.
Se la funzione / metodo desidera indicare che potrebbe generare un errore, dovrebbe contenere una throws
parola chiave come questa
func summonDefaultDragon() throws -> Dragon
Nota: non esiste una specifica per il tipo di errore che la funzione può effettivamente generare. Questa dichiarazione afferma semplicemente che la funzione può generare un'istanza di qualsiasi tipo che implementa ErrorType o che non sta eseguendo affatto.
Per invocare la funzione è necessario utilizzare la parola chiave try, in questo modo
try summonDefaultDragon()
questa linea dovrebbe normalmente essere presente blocco di cattura come questo
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
}
Nota: la clausola catch utilizza tutte le potenti funzionalità di corrispondenza del modello Swift in modo da essere molto flessibili qui.
Puoi decidere di propagare l'errore, se stai chiamando una funzione di lancio da una funzione che è essa stessa contrassegnata con la throws
parola chiave:
func fulfill(quest: Quest) throws {
let dragon = try summonDefaultDragon()
quest.ride(dragon)
}
In alternativa, puoi chiamare la funzione di lancio usando try?
:
let dragonOrNil = try? summonDefaultDragon()
In questo modo si ottiene il valore restituito o zero, se si è verificato un errore. In questo modo non si ottiene l'oggetto errore.
Ciò significa che puoi anche combinare try?
con dichiarazioni utili come:
if let dragon = try? summonDefaultDragon()
o
guard let dragon = try? summonDefaultDragon() else { ... }
Infine, puoi decidere di sapere che l'errore non si verificherà effettivamente (ad es. Perché hai già verificato i prerequisiti) e utilizzare la try!
parola chiave:
let dragon = try! summonDefaultDragon()
Se la funzione genera effettivamente un errore, verrà visualizzato un errore di runtime nell'applicazione e l'applicazione verrà chiusa.
Per generare un errore, utilizzare la parola chiave throw in questo modo
throw DragonError.dragonIsMissing
Puoi lanciare qualsiasi cosa conforme al ErrorType
protocollo. Per i principianti è NSError
conforme a questo protocollo ma probabilmente ti piacerebbe andare con enum-based ErrorType
che ti consente di raggruppare più errori correlati, potenzialmente con ulteriori pezzi di dati, come questo
enum DragonError: ErrorType {
case dragonIsMissing
case notEnoughMana(requiredMana: Int)
...
}
Le principali differenze tra il nuovo meccanismo di errore Swift 2 e 3 e le eccezioni di stile Java / C # / C ++ sono le seguenti:
do-catch
+ try
+ defer
rispetto alla try-catch-finally
sintassi tradizionale .do-catch
blocco non catturerà alcuna NSException e viceversa, per questo devi usare ObjC.NSError
convenzioni del metodo Cocoa di restituzione false
(per Bool
funzioni di ritorno) o nil
(per AnyObject
funzioni di ritorno) e passaggio NSErrorPointer
con dettagli di errore.Come ulteriore zucchero sintetico per facilitare la gestione degli errori, ci sono altri due concetti
defer
parola chiave) che consentono di ottenere lo stesso effetto dei blocchi infine in Java / C # / eccguard
parola chiave) che ti consente di scrivere un po 'meno codice if / else rispetto al normale codice di controllo / segnalazione degli errori.Errori di runtime:
Come suggerisce Leandros per la gestione degli errori di runtime (come problemi di connettività di rete, analisi dei dati, apertura del file, ecc.), Dovresti usare NSError
come hai fatto in ObjC, perché Foundation, AppKit, UIKit, ecc. Riportano i loro errori in questo modo. Quindi è più una cosa quadro che una cosa linguistica.
Un altro modello frequente che viene utilizzato sono i blocchi di successo / fallimento del separatore come in 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")
})
Tuttavia il blocco di errori ha ricevuto frequentemente l' NSError
istanza, descrivendo l'errore.
Errori del programmatore:
Per errori del programmatore (come accesso fuori limite dell'elemento dell'array, argomenti non validi passati a una chiamata di funzione, ecc.) Hai usato le eccezioni in ObjC. La lingua rapida non sembra avere alcun supporto linguistico per le eccezioni ( parola chiave throw
, come catch
, ecc.). Tuttavia, poiché la documentazione suggerisce che è in esecuzione sullo stesso runtime di ObjC, e quindi sei ancora in grado di lanciare in NSExceptions
questo modo:
NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()
Non puoi catturarli in puro Swift, anche se puoi optare per la cattura di eccezioni nel codice ObjC.
La domanda è se dovresti generare eccezioni per errori del programmatore o piuttosto usare affermazioni come Apple suggerisce nella guida linguistica.
fatalError(...)
è lo stesso pure.
Aggiornamento del 9 giugno 2015 - Molto importante
Swift 2.0 viene fornito con try
, throw
e catch
parole chiave e il più emozionante è:
Swift traduce automaticamente i metodi Objective-C che producono errori in metodi che generano un errore in base alla funzionalità di gestione degli errori nativa di Swift.
Nota: i metodi che utilizzano errori, come i metodi delegati o i metodi che accettano un gestore di completamento con un argomento oggetto NSError, non diventano metodi che vengono generati quando importati da Swift.
Estratto da: Apple Inc. "Utilizzo di Swift con Cocoa e Objective-C (Prelavaggio Swift 2)." iBook.
Esempio: (dal libro)
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);
}
L'equivalente in swift sarà:
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)")
}
Lancio di un errore:
*errorPtr = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotOpenFile userInfo: nil]
Verrà automaticamente propagato al chiamante:
throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, userInfo: nil)
Dai libri di Apple, The Swift Programming Language sembra che gli errori debbano essere gestiti usando enum.
Ecco un esempio dal libro.
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)"
}
Da: Apple Inc. "Il linguaggio di programmazione Swift". iBook. https://itun.es/br/jEUH0.l
Aggiornare
Dai libri di Apple, "Utilizzo di Swift con Cocoa e Objective-C". Le eccezioni di runtime non si verificano utilizzando linguaggi rapidi, quindi è per questo che non hai try-catch. Invece si utilizza il concatenamento opzionale .
Ecco un tratto dal libro:
Ad esempio, nel seguente elenco di codici, la prima e la seconda riga non vengono eseguite perché la proprietà length e il metodo characterAtIndex: non esistono su un oggetto NSDate. La costante myLength è inferita come Int opzionale ed è impostata su zero. È inoltre possibile utilizzare un'istruzione if-let per scartare in modo condizionale il risultato di un metodo a cui l'oggetto potrebbe non rispondere, come mostrato nella riga tre
let myLength = myObject.length?
let myChar = myObject.characterAtIndex?(5)
if let fifthCharacter = myObject.characterAtIndex(5) {
println("Found \(fifthCharacter) at index 5")
}
Estratto da: Apple Inc. "Utilizzo di Swift con Cocoa e Objective-C". iBook. https://itun.es/br/1u3-0.l
E i libri ti incoraggiano anche a utilizzare il modello di errore del cacao da Objective-C (NSError Object)
La segnalazione degli errori in Swift segue lo stesso modello che ha in Objective-C, con l'ulteriore vantaggio di offrire valori di ritorno opzionali. Nel caso più semplice, si restituisce un valore Bool dalla funzione per indicare se è riuscito o meno. Quando è necessario segnalare il motivo dell'errore, è possibile aggiungere alla funzione un parametro di uscita NSError di tipo NSErrorPointer. Questo tipo è approssimativamente equivalente a NSError ** di Objective-C, con ulteriore sicurezza della memoria e digitazione opzionale. È possibile utilizzare il prefisso e l'operatore per passare un riferimento a un tipo NSError opzionale come oggetto NSErrorPointer, come mostrato nell'elenco di codici riportato di seguito.
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)")
}
}
Estratto da: Apple Inc. "Utilizzo di Swift con Cocoa e Objective-C". iBook. https://itun.es/br/1u3-0.l
Non ci sono eccezioni in Swift, simile all'approccio dell'Obiettivo-C.
In fase di sviluppo, è possibile utilizzare assert
per rilevare eventuali errori che potrebbero apparire e che devono essere corretti prima di passare alla produzione.
L' NSError
approccio classico non viene modificato, si invia un NSErrorPointer
, che viene popolato.
Breve esempio:
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();
diventa f(&err);if(err) return;g(&err);if(err) return;
per il primo mese, poi diventaf(nil);g(nil);hopeToGetHereAlive();
Il 'Swift Way' raccomandato è:
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
}
Tuttavia preferisco provare / catturare poiché trovo più facile da seguire perché sposta la gestione degli errori in un blocco separato alla fine, questa disposizione viene talvolta chiamata "Percorso d'oro". Fortunatamente puoi farlo con le chiusure:
TryBool {
write("~/Error2")(error: $0) // The code to try
}.catch {
println("write failure 2: \($0!.localizedDescription)") // Report failure
// assert(false) // Terminate program
}
Inoltre è facile aggiungere una funzione di nuovo tentativo:
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
}
L'elenco per 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)
}
}
È possibile scrivere una classe simile per testare un valore restituito facoltativo invece del valore 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)
}
}
La versione TryOptional applica un tipo di ritorno non opzionale che semplifica la successiva programmazione, ad esempio '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
Utilizzando 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
}
Nota disimballaggio automatico.
Modifica: sebbene questa risposta funzioni, è poco più di Objective-C traslitterata in Swift. È stato reso obsoleto dalle modifiche in Swift 2.0. La risposta di Guilherme Torres Castro sopra è un'ottima introduzione al modo preferito di gestire gli errori in Swift. VOS
Ci è voluto un po 'per capirlo, ma penso di averlo aiutato. Sembra brutto però. Nient'altro che una skin sottile rispetto alla versione Objective-C.
Chiamata di una funzione con un parametro 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
}`
Scrittura della funzione che accetta un parametro di errore ...
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...
}
Involucro di base attorno all'obiettivo C che ti offre la funzione di prova di cattura. https://github.com/williamFalcon/SwiftTryCatch
Usa come:
SwiftTryCatch.try({ () -> Void in
//try something
}, catch: { (error) -> Void in
//handle error
}, finally: { () -> Void in
//close resources
})
Questa è una risposta di aggiornamento per swift 2.0. Non vedo l'ora che funzioni ricco di modelli di gestione degli errori come in Java. Alla fine hanno annunciato la buona notizia. Qui
Modello di gestione degli errori: il nuovo modello di gestione degli errori in Swift 2.0 sembrerà immediatamente naturale, con parole chiave di prova, lancio e acquisizione familiari . Soprattutto, è stato progettato per funzionare perfettamente con gli SDK di Apple e NSError. In effetti, NSError è conforme a ErrorType di Swift. Avrai sicuramente voglia di guardare la sessione del WWDC su Novità di Swift per saperne di più.
per esempio :
func loadData() throws { }
func test() {
do {
try loadData()
} catch {
print(error)
}}
Come ha detto Guilherme Torres Castro, a Swift 2.0, try
, catch
, do
possono essere utilizzati nella programmazione.
Ad esempio, in CoreData recuperare il metodo di dati, invece di mettere &error
come parametro in managedContext.executeFetchRequest(fetchRequest, error: &error)
, ora abbiamo solo bisogno di utilizzare l'uso managedContext.executeFetchRequest(fetchRequest)
e poi gestire l'errore con try
, catch
( mela Document Link )
do {
let fetchedResults = try managedContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]
if let results = fetchedResults{
people = results
}
} catch {
print("Could not fetch")
}
Se hai già scaricato xcode7 Beta. Prova a cercare errori di lancio in Documentazione e Riferimento API e scegli il primo risultato che mostra, ti dà un'idea di base su cosa si può fare per questa nuova sintassi. Tuttavia, la documentazione completa non è ancora disponibile per molte API.
Altre tecniche di gestione degli errori più elaborate sono disponibili in
Novità di Swift (Sessione 2015 106 28m30s)
La gestione degli errori è una nuova funzionalità di Swift 2.0. Esso utilizza i try
, throw
e catch
le parole chiave.
Vedi l' annuncio di Apple Swift 2.0 sul blog ufficiale di Apple Swift
Lib piacevole e semplice da gestire eccezione: TryCatchFinally-Swift
Come pochi altri si avvolge attorno alle caratteristiche dell'eccezione C dell'obiettivo.
Usalo in questo modo:
try {
println(" try")
}.catch { e in
println(" catch")
}.finally {
println(" finally")
}
A partire da Swift 2, come altri hanno già accennato, la gestione degli errori si ottiene meglio attraverso l'uso delle enumerazioni do / try / catch e ErrorType. Funziona abbastanza bene per i metodi sincroni, ma è necessaria un po 'di intelligenza per la gestione asincrona degli errori.
Questo articolo ha un ottimo approccio a questo problema:
https://jeremywsherman.com/blog/2015/06/17/using-swift-throws-with-completion-callbacks/
Riassumere:
// 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()
}
quindi, la chiamata al metodo sopra sarebbe la seguente:
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
}
})
Sembra un po 'più pulito che avere un callback di ErrorHandler separato passato alla funzione asincrona, che era come sarebbe stato gestito prima di Swift 2.
Quello che ho visto è che a causa della natura del dispositivo non vuoi lanciare un sacco di errori criptici nella gestione dei messaggi all'utente. Questo è il motivo per cui la maggior parte delle funzioni restituisce valori opzionali, quindi basta codificare per ignorare il facoltativo. Se una funzione ritorna nulla, il che significa che ha fallito, puoi far apparire un messaggio o altro.