J'ai une fonction qui jette une erreur. Dans cette fonction, j'ai une fermeture inside a
dont j'ai besoin pour jeter l'erreur à partir de son gestionnaire d'achèvement. Est-ce possible ?
Voici mon code jusqu'à présent.
enum CalendarEventError: ErrorType {
case UnAuthorized
case AccessDenied
case Failed
}
func insertEventToDefaultCalendar(event :EKEvent) throws {
let eventStore = EKEventStore()
switch EKEventStore.authorizationStatusForEntityType(.Event) {
case .Authorized:
do {
try insertEvent(eventStore, event: event)
} catch {
throw CalendarEventError.Failed
}
case .Denied:
throw CalendarEventError.AccessDenied
case .NotDetermined:
eventStore.requestAccessToEntityType(EKEntityType.Event, completion: { (granted, error) -> Void in
if granted {
//insertEvent(eventStore)
} else {
//throw CalendarEventError.AccessDenied
}
})
default:
}
}
Lorsque vous définissez une fermeture qui jette:
enum MyError: ErrorType {
case Failed
}
let closure = {
throw MyError.Failed
}
alors le type de cette fermeture est () throws -> ()
et la fonction qui prend cette fermeture comme paramètre doit avoir le même type de paramètre:
func myFunction(completion: () throws -> ()) {
}
Si cette fonction vous pouvez appeler completion
fermeture synchrone:
func myFunction(completion: () throws -> ()) throws {
completion()
}
et vous devez ajouter le mot clé throws
à la signature de fonction ou à la complétion d'appel avec try!
:
func myFunction(completion: () throws -> ()) {
try! completion()
}
ou asynchrone:
func myFunction(completion: () throws -> ()) {
dispatch_async(dispatch_get_main_queue(), { try! completion() })
}
Dans le dernier cas, vous ne pourrez pas attraper d'erreur.
Donc, si la clôture completion
dans la méthode eventStore.requestAccessToEntityType
et la méthode elle-même n’a pas de signature throws
ou si completion
est appelé de manière asynchrone, vous ne pouvez pas throw
à partir de cette fermeture.
Je vous suggère l'implémentation suivante de votre fonction qui transmet l'erreur au rappel au lieu de la lancer:
func insertEventToDefaultCalendar(event: EKEvent, completion: CalendarEventError? -> ()) {
let eventStore = EKEventStore()
switch EKEventStore.authorizationStatusForEntityType(.Event) {
case .Authorized:
do {
try insertEvent(eventStore, event: event)
} catch {
completion(CalendarEventError.Failed)
}
case .Denied:
completion(CalendarEventError.AccessDenied)
case .NotDetermined:
eventStore.requestAccessToEntityType(EKEntityType.Event, completion: { (granted, error) -> Void in
if granted {
//insertEvent(eventStore)
} else {
completion(CalendarEventError.AccessDenied)
}
})
default:
}
}
Ce n'est pas possible dans ce cas - ce gestionnaire d'achèvement devrait être déclaré avec throws
(et la méthode avec rethrows
) et celle-ci ne l'est pas.
Notez que tout ce que jette est juste une notation différente pour NSError **
dans Objective-C (paramètre d'erreur inout). Le rappel Objective-C n'a pas de paramètre inout, il n'y a donc aucun moyen de transmettre l'erreur.
Vous devrez utiliser une méthode différente pour gérer les erreurs.
En général, NSError **
dans Obj-C ou throws
dans Swift ne fonctionne pas bien avec les méthodes asynchrones, car la gestion des erreurs fonctionne de manière synchrone.
Parce que lancer est synchrone, une fonction asynchrone qui veut lancer doit avoir une fermeture interne qui lance, comme ceci:
func insertEventToDefaultCalendar(event :EKEvent, completion: (() throws -> Void) -> Void) {
let eventStore = EKEventStore()
switch EKEventStore.authorizationStatusForEntityType(.Event) {
case .Authorized:
do {
try insertEvent(eventStore, event: event)
completion { /*Success*/ }
} catch {
completion { throw CalendarEventError.Failed }
}
case .Denied:
completion { throw CalendarEventError.AccessDenied }
case .NotDetermined:
eventStore.requestAccessToEntityType(EKEntityType.Event, completion: { (granted, error) -> Void in
if granted {
let _ = try? self.insertEvent(eventStore, event: event)
completion { /*Success*/ }
} else {
completion { throw CalendarEventError.AccessDenied }
}
})
default:
break
}
}
Ensuite, sur le site d’appel, vous l’utilisez comme ceci:
insertEventToDefaultCalendar(EKEvent()) { response in
do {
try response()
// Success
}
catch {
// Error
print(error)
}
}
requestAccessToEntityType
fait son travail de manière asynchrone. Lorsque le gestionnaire d'achèvement est finalement exécuté, votre fonction est déjà renvoyée. Par conséquent, il est impossible de rejeter une erreur de la fermeture comme vous le suggérez.
Vous devriez probablement refactoriser le code afin que la partie autorisation soit gérée séparément de l'insertion d'événement et appelez uniquement insertEventToDefaultCalendar
lorsque vous savez que le statut de l'autorisation est tel que prévu/requis.
Si vous voulez vraiment gérer chaque opération dans une fonction, vous pouvez utiliser un sémaphore (ou une technique similaire) afin que la partie de code asynchrone se comporte de manière synchrone par rapport à votre fonction.
func insertEventToDefaultCalendar(event :EKEvent) throws {
var accessGranted: Bool = false
let eventStore = EKEventStore()
switch EKEventStore.authorizationStatusForEntityType(.Event) {
case .Authorized:
accessGranted = true
case .Denied, .Restricted:
accessGranted = false
case .NotDetermined:
let semaphore = dispatch_semaphore_create(0)
eventStore.requestAccessToEntityType(EKEntityType.Event, completion: { (granted, error) -> Void in
accessGranted = granted
dispatch_semaphore_signal(semaphore)
})
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}
if accessGranted {
do {
try insertEvent(eventStore, event: event)
} catch {
throw CalendarEventError.Failed
}
}
else {
throw CalendarEventError.AccessDenied
}
}
Vous ne pouvez pas faire fonctionner avec throw
, mais renvoyez une closure
avec un statut ou une erreur! S'il n'est pas clair, je peux donner du code.