web-dev-qa-db-fra.com

Coup rapide de la fermeture imbriqué dans une fonction

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:
    }
}
13
shannoga

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:
    }
}
8
mixel

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.

6
Sulthan

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)
        }
    }
5
nobre

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
    }
}
0
iOSX

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.

0
katleta3000