Je n'ai pas trop lu Swift, mais une chose que j'ai remarquée est qu'il n'y a pas d'exceptions. Alors, comment font-ils la gestion des erreurs dans Swift? Quelqu'un a-t-il trouvé quelque chose concernant la gestion des erreurs?
Je n'ai pas trop lu Swift, mais une chose que j'ai remarquée est qu'il n'y a pas d'exceptions. Alors, comment font-ils la gestion des erreurs dans Swift? Quelqu'un a-t-il trouvé quelque chose concernant la gestion des erreurs?
Réponses:
Les choses ont un peu changé dans Swift 2, car il existe un nouveau mécanisme de gestion des erreurs, qui est un peu plus similaire aux exceptions mais différent en détail.
Si la fonction / méthode veut indiquer qu'elle peut générer une erreur, elle doit contenir un throws
mot clé comme celui-ci
func summonDefaultDragon() throws -> Dragon
Remarque: il n'y a pas de spécification du type d'erreur que la fonction peut réellement générer. Cette déclaration indique simplement que la fonction peut lancer une instance de n'importe quel type implémentant ErrorType ou ne la lance pas du tout.
Pour appeler la fonction, vous devez utiliser le mot clé try, comme celui-ci
try summonDefaultDragon()
cette ligne devrait normalement être présente dans un bloc do-catch comme celui-ci
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
}
Remarque: la clause catch utilise toutes les fonctionnalités puissantes de la correspondance de motifs Swift, vous êtes donc très flexible ici.
Vous pouvez décider de propager l'erreur, si vous appelez une fonction de lancement à partir d'une fonction qui est elle-même marquée par le throws
mot-clé:
func fulfill(quest: Quest) throws {
let dragon = try summonDefaultDragon()
quest.ride(dragon)
}
Vous pouvez également appeler la fonction de lancement en utilisant try?
:
let dragonOrNil = try? summonDefaultDragon()
De cette façon, vous obtenez la valeur de retour ou nil, si une erreur s'est produite. En utilisant cette méthode, vous n'obtenez pas l'objet d'erreur.
Ce qui signifie que vous pouvez également combiner try?
avec des déclarations utiles telles que:
if let dragon = try? summonDefaultDragon()
ou
guard let dragon = try? summonDefaultDragon() else { ... }
Enfin, vous pouvez décider que vous savez que l'erreur ne se produira pas réellement (par exemple parce que vous avez déjà vérifié les prérequis) et utiliser le try!
mot-clé:
let dragon = try! summonDefaultDragon()
Si la fonction génère réellement une erreur, vous obtiendrez une erreur d'exécution dans votre application et l'application se terminera.
Pour lancer une erreur, vous utilisez un mot-clé throw comme celui-ci
throw DragonError.dragonIsMissing
Vous pouvez lancer tout ce qui est conforme au ErrorType
protocole. Pour commencer, il NSError
est conforme à ce protocole, mais vous aimeriez probablement utiliser enum-based ErrorType
qui vous permet de regrouper plusieurs erreurs liées, potentiellement avec des données supplémentaires, comme celle-ci
enum DragonError: ErrorType {
case dragonIsMissing
case notEnoughMana(requiredMana: Int)
...
}
Les principales différences entre le nouveau mécanisme d'erreur Swift 2 & 3 et les exceptions de style Java / C # / C ++ sont les suivantes:
do-catch
+ try
+ defer
vs traditionnelletry-catch-finally
syntaxe .do-catch
bloc n'attrapera aucune NSException, et vice versa, pour cela vous devez utiliser ObjC.NSError
conventions de la méthode Cocoa consistant à retourner false
(pour Bool
renvoyer des fonctions) ou nil
(pour AnyObject
renvoyer des fonctions) et à transmettre NSErrorPointer
avec les détails de l'erreur.En tant que sucre syntaxique supplémentaire pour faciliter la gestion des erreurs, il existe deux autres concepts
defer
mot-clé) qui vous permettent d'obtenir le même effet que les blocs finally en Java / C # / etcguard
mot-clé) qui vous permet d'écrire un peu moins de code if / else que dans le code de vérification / signalisation d'erreur normal.Erreurs d'exécution:
Comme Leandros le suggère pour gérer les erreurs d'exécution (comme les problèmes de connectivité réseau, l'analyse des données, l'ouverture de fichier, etc.), vous devez utiliser NSError
comme vous l'avez fait dans ObjC, car la Fondation, AppKit, UIKit, etc. signalent leurs erreurs de cette manière. C'est donc plus une question de cadre que de langage.
Un autre modèle fréquemment utilisé est celui des blocs de succès / échec de séparateur comme dans 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")
})
Toujours le bloc d'échec a fréquemment reçu l' NSError
instance, décrivant l'erreur.
Erreurs de programmeur:
Pour les erreurs de programmeur (comme l'accès hors limites à l'élément de tableau, les arguments non valides passés à un appel de fonction, etc.), vous avez utilisé des exceptions dans ObjC. Swift langue ne semble pas avoir de prise en charge linguistique des exceptions (comme throw
, catch
, etc mot - clé). Cependant, comme la documentation le suggère, il s'exécute sur le même runtime qu'ObjC, et vous pouvez donc toujours lancer NSExceptions
comme ceci:
NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()
Vous ne pouvez tout simplement pas les attraper en Swift pur, bien que vous puissiez opter pour intercepter les exceptions dans le code ObjC.
La question est de savoir si vous devez lever des exceptions pour les erreurs du programmeur, ou plutôt utiliser des assertions comme le suggère Apple dans le guide du langage.
fatalError(...)
est le même aussi bien.
Mise à jour du 9 juin 2015 - Très important
Swift 2.0 est livré avec try
, throw
et des catch
mots - clés et le plus excitant est la suivante :
Swift traduit automatiquement les méthodes Objective-C qui produisent des erreurs en méthodes qui génèrent une erreur selon la fonctionnalité native de gestion des erreurs de Swift.
Remarque: les méthodes qui consomment des erreurs, telles que les méthodes déléguées ou les méthodes qui prennent un gestionnaire d'achèvement avec un argument d'objet NSError, ne deviennent pas des méthodes qui se lancent lorsqu'elles sont importées par Swift.
Extrait de: Apple Inc. «Utilisation de Swift avec Cocoa et Objective-C (version préliminaire de Swift 2)». iBooks.
Exemple: (du livre)
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'équivalent en swift sera:
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)")
}
Lancer une erreur:
*errorPtr = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotOpenFile userInfo: nil]
Sera automatiquement propagé à l'appelant:
throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, userInfo: nil)
D'après les livres Apple, The Swift Programming Language, il semble que les erreurs devraient être gérées à l'aide d'énumération.
Voici un exemple du livre.
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)"
}
De: Apple Inc. «The Swift Programming Language». iBooks. https://itun.es/br/jEUH0.l
Mettre à jour
Tiré des livres d'actualités Apple, "Utilisation de Swift avec Cocoa et Objective-C". Les exceptions d'exécution ne se produisent pas en utilisant des langages Swift, c'est pourquoi vous n'avez pas try-catch. À la place, vous utilisez le chaînage facultatif .
Voici un extrait du livre:
Par exemple, dans la liste de codes ci-dessous, les première et deuxième lignes ne sont pas exécutées car la propriété length et la méthode characterAtIndex: n'existent pas sur un objet NSDate. La constante myLength est supposée être un Int facultatif et est définie sur nil. Vous pouvez également utiliser une instruction if – let pour dérouler conditionnellement le résultat d'une méthode à laquelle l'objet peut ne pas répondre, comme indiqué à la ligne trois
let myLength = myObject.length?
let myChar = myObject.characterAtIndex?(5)
if let fifthCharacter = myObject.characterAtIndex(5) {
println("Found \(fifthCharacter) at index 5")
}
Extrait de: Apple Inc. «Utilisation de Swift avec Cocoa et Objective-C». iBooks. https://itun.es/br/1u3-0.l
Et les livres vous encouragent également à utiliser le modèle d'erreur cacao d'Objective-C (NSError Object)
Le rapport d'erreurs dans Swift suit le même modèle que dans Objective-C, avec l'avantage supplémentaire d'offrir des valeurs de retour facultatives. Dans le cas le plus simple, vous renvoyez une valeur booléenne à partir de la fonction pour indiquer si elle a réussi ou non. Lorsque vous devez signaler la raison de l'erreur, vous pouvez ajouter à la fonction un paramètre NSError out de type NSErrorPointer. Ce type est à peu près équivalent à NSError ** d'Objective-C, avec une sécurité de mémoire supplémentaire et un typage facultatif. Vous pouvez utiliser le préfixe & opérateur pour transmettre une référence à un type NSError facultatif en tant qu'objet NSErrorPointer, comme indiqué dans la liste de codes ci-dessous.
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)")
}
}
Extrait de: Apple Inc. «Utilisation de Swift avec Cocoa et Objective-C». iBooks. https://itun.es/br/1u3-0.l
Il n'y a pas d'exceptions dans Swift, similaire à l'approche d'Objective-C.
En développement, vous pouvez utiliser assert
pour détecter les erreurs qui pourraient apparaître et qui doivent être corrigées avant de passer en production.
L' NSError
approche classique n'est pas modifiée, vous envoyez unNSErrorPointer
, qui se remplit.
Bref exemple:
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();
devient f(&err);if(err) return;g(&err);if(err) return;
pour le premier mois, puis il devient justef(nil);g(nil);hopeToGetHereAlive();
Le 'Swift Way' recommandé est:
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
}
Cependant, je préfère try / catch car je trouve cela plus facile à suivre car il déplace la gestion des erreurs vers un bloc séparé à la fin, cet arrangement est parfois appelé "Golden Path". Heureusement, vous pouvez le faire avec des fermetures:
TryBool {
write("~/Error2")(error: $0) // The code to try
}.catch {
println("write failure 2: \($0!.localizedDescription)") // Report failure
// assert(false) // Terminate program
}
Il est également facile d'ajouter une fonction de nouvelle tentative:
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
}
La liste de TryBool est:
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)
}
}
Vous pouvez écrire une classe similaire pour tester une valeur renvoyée facultative au lieu d'une valeur booléenne:
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 version TryOptional applique un type de retour non optionnel qui facilite la programmation ultérieure, par exemple '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
Utilisation de 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
}
Notez le déballage automatique.
Edit: Bien que cette réponse fonctionne, ce n'est guère plus qu'Objective-C translittéré en Swift. Il a été rendu obsolète par les modifications apportées à Swift 2.0. La réponse de Guilherme Torres Castro ci-dessus est une très bonne introduction à la manière préférée de gérer les erreurs dans Swift. VOS
Il a fallu un peu de temps pour le comprendre, mais je pense que je l'ai compris. Cela semble moche cependant. Rien de plus qu'une peau fine sur la version Objective-C.
Appel d'une fonction avec un paramètre 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
}`
Ecriture de la fonction qui prend un paramètre d'erreur ...
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...
}
Emballage de base autour de l'objectif C qui vous donne la fonction try catch. https://github.com/williamFalcon/SwiftTryCatch
Utilisez comme:
SwiftTryCatch.try({ () -> Void in
//try something
}, catch: { (error) -> Void in
//handle error
}, finally: { () -> Void in
//close resources
})
Ceci est une réponse de mise à jour pour swift 2.0. Je suis impatient d'avoir un modèle de gestion des erreurs riche en fonctionnalités, comme en java. Enfin, ils ont annoncé la bonne nouvelle. ici
Modèle de gestion des erreurs: le nouveau modèle de gestion des erreurs de Swift 2.0 se sentira instantanément naturel, avec des mots-clés try, throw et catch familiers . Mieux encore, il a été conçu pour fonctionner parfaitement avec les SDK Apple et NSError. En fait, NSError est conforme à ErrorType de Swift. Vous voudrez certainement regarder la session de la WWDC sur les nouveautés de Swift pour en savoir plus.
par exemple :
func loadData() throws { }
func test() {
do {
try loadData()
} catch {
print(error)
}}
Comme Guilherme Torres Castro a dit, à Swift 2.0, try
, catch
,do
peut être utilisé dans la programmation.
Par exemple, dans la méthode de récupération de données CoreData, au lieu de le placer en &error
tant que paramètre dans le managedContext.executeFetchRequest(fetchRequest, error: &error)
, il ne nous reste plus qu'à utiliser use managedContext.executeFetchRequest(fetchRequest)
, puis à gérer l'erreur avec 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")
}
Si vous avez déjà téléchargé la version bêta de xcode7. Essayez de rechercher des erreurs de lancement dans la documentation et la référence d'API et choisissez le premier résultat affiché, cela donne une idée de base de ce qui peut être fait pour cette nouvelle syntaxe. Cependant, la documentation complète n'est pas encore publiée pour de nombreuses API.
Des techniques plus sophistiquées de gestion des erreurs peuvent être trouvées dans
Quoi de neuf dans Swift (2015 Session 106 28m30s)
La gestion des erreurs est une nouvelle fonctionnalité de Swift 2.0. Il utilise les try
, throw
et les catch
mots clés.
Voir l' annonce Apple Swift 2.0 sur le blog officiel Apple Swift
Belle et simple bibliothèque pour gérer l'exception: TryCatchFinally-Swift
Comme quelques autres, il englobe les fonctionnalités d'exception de l'objectif C.
Utilisez-le comme ceci:
try {
println(" try")
}.catch { e in
println(" catch")
}.finally {
println(" finally")
}
À partir de Swift 2, comme d'autres l'ont déjà mentionné, la gestion des erreurs est mieux accomplie en utilisant les énumérations do / try / catch et ErrorType. Cela fonctionne assez bien pour les méthodes synchrones, mais un peu d'intelligence est nécessaire pour la gestion des erreurs asynchrones.
Cet article a une excellente approche de ce problème:
https://jeremywsherman.com/blog/2015/06/17/using-swift-throws-with-completion-callbacks/
Résumer:
// 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()
}
puis, l'appel à la méthode ci-dessus serait le suivant:
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
}
})
Cela semble un peu plus propre que d'avoir un rappel errorHandler séparé passé à la fonction asynchrone, ce qui était la façon dont cela serait géré avant Swift 2.
Ce que j'ai vu, c'est qu'en raison de la nature de l'appareil, vous ne voulez pas lancer un tas de messages de gestion d'erreurs cryptiques à l'utilisateur. C'est pourquoi la plupart des fonctions renvoient des valeurs facultatives, puis vous codez simplement pour ignorer l'option. Si une fonction revient à zéro, ce qui signifie qu'elle a échoué, vous pouvez afficher un message ou autre.