Быстрый бросок из закрытия, вложенного в функцию

У меня есть функция, которая выдает ошибку, в этой функции у меня есть закрытие inside a, которое мне нужно, чтобы выдать ошибку из обработчика завершения. Это возможно ?

Вот мой код до сих пор.

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

person shannoga    schedule 19.10.2015    source источник
comment
Вы можете сохранить логическое значение за пределами части eventStore. и изменить его внутри вместо того, чтобы выдавать ошибку, а затем проверить логическое значение снаружи и создать исключение.   -  person Arc676    schedule 19.10.2015
comment
@ Arc676 Если закрытие completion вызывается асинхронно, то это невозможно, потому что insertEventToDefaultCalendar вернется до вызова completion.   -  person mixel    schedule 19.10.2015
comment
@shannoga Я обновил свой ответ, указав обходной путь для вашего случая.   -  person mixel    schedule 19.10.2015


Ответы (5)


Когда вы определяете закрытие, которое выдает:

enum MyError: ErrorType {
    case Failed
}

let closure = {
    throw MyError.Failed
}

тогда тип этого замыкания — () throws -> (), и функция, которая принимает это замыкание в качестве параметра, должна иметь тот же тип параметра:

func myFunction(completion: () throws -> ()) {
}

В этой функции вы можете вызвать completion синхронное закрытие:

func myFunction(completion: () throws -> ()) throws {
    completion() 
}

и вам нужно добавить ключевое слово throws в сигнатуру функции или завершить вызов с помощью try!:

func myFunction(completion: () throws -> ()) {
    try! completion() 
}

или асинхронный:

func myFunction(completion: () throws -> ()) {
    dispatch_async(dispatch_get_main_queue(), { try! completion() })
}

В последнем случае вы не сможете поймать ошибку.

Итак, если completion замыкание в eventStore.requestAccessToEntityType методе и сам метод не имеет throws в своей сигнатуре или если completion вызывается асинхронно, то вы не можете throw из этого замыкания.

Я предлагаю вам следующую реализацию вашей функции, которая передает ошибку обратному вызову, а не выдает ее:

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:
    }
}
person mixel    schedule 19.10.2015
comment
Опечатка в первом поле кода, должно быть MyError, а не TextError. - person RenniePet; 19.01.2017
comment
@RenniePet Обновлено. Спасибо. - person mixel; 19.01.2017

Поскольку генерация является синхронной, асинхронная функция, которая хочет генерировать генерацию, должна иметь внутреннее замыкание, которое генерирует генерацию, например:

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

Затем на сайте вызова вы используете его следующим образом:

   insertEventToDefaultCalendar(EKEvent()) { response in
        do {
            try response()
            // Success
        }
        catch {
            // Error
            print(error)
        }
    }
person Rafael Nobre    schedule 13.06.2016

В данном случае это невозможно — этот обработчик завершения должен быть объявлен с throws (и метод с rethrows), а этот — нет.

Обратите внимание, что все эти броски — это просто разные обозначения для NSError ** в Objective-C (параметр ошибки inout). Обратный вызов Objective-C не имеет параметра inout, поэтому нет возможности передать ошибку.

Вам придется использовать другой метод для обработки ошибок.

В общем, NSError ** в Obj-C или throws в Swift плохо сочетаются с асинхронными методами, потому что обработка ошибок работает синхронно.

person Sulthan    schedule 19.10.2015

Вы не можете сделать функцию с throw, но вернуть closure со статусом или ошибкой! Если непонятно, могу дать код.

person katleta3000    schedule 19.10.2015

requestAccessToEntityType работает асинхронно. Когда обработчик завершения в конечном итоге запустит вашу функцию, уже возвращенную. Поэтому невозможно выдать ошибку из закрытия так, как вы предлагаете.

Вероятно, вам следует реорганизовать код, чтобы часть авторизации обрабатывалась отдельно от вставки события и вызывала insertEventToDefaultCalendar только тогда, когда вы знаете, что статус авторизации соответствует ожидаемому/требуемому.

Если вы действительно хотите обрабатывать все в одной функции, вы можете использовать семафор (или аналогичный метод), чтобы часть асинхронного кода вела себя синхронно по отношению к вашей функции.

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
    }
}
person iOSX    schedule 19.10.2015
comment
Использование semaphore действительно плохая идея, потому что insertEventToDefaultCalendar и completion закрытие вызывается в одном и том же потоке, и этот поток будет заблокирован после dispatch_semaphore_wait, поэтому completion и другой код никогда не будут вызываться. - person mixel; 19.10.2015
comment
Проверка устройства также прошла успешно. - person iOSX; 19.10.2015
comment
Я протестировал этот код gist.github.com/mxl/dc501fd93074dda110ff на игровой площадке и в качестве команды OS X. проект линейного инструмента. Он печатает 1, но никогда не печатает 2. - person mixel; 19.10.2015
comment
Протестировал его на устройстве iOS - тот же результат, он никогда не печатает 2. - person mixel; 19.10.2015
comment
Я могу подтвердить, что ваш код действительно блокирует поток, но если вы протестируете мой фрагмент кода, вы заметите, что он работает нормально ;) - person iOSX; 19.10.2015
comment
Я проверил ваш фрагмент кода. Я устанавливаю точки останова в строках accessGranted = granted и if accessGranted {, и выполнение никогда не останавливается на них, если я вызываю insertEventToDefaultCalendar из UIViewController.viewDidLoad. - person mixel; 19.10.2015
comment
Он даже не показывает диалог с запросом на доступ, пока я не остановлю сеанс отладки и не убью приложение. - person mixel; 19.10.2015
comment
Как уже было сказано, у меня работает нормально. В любом случае, я изменил вашу суть, так что обработчик завершения выполняется в другой очереди, а затем он тоже работает, т.е. печатается 2. pastebin.com/E3rcC9NJ - person iOSX; 19.10.2015
comment
Интересно, как у вас все нормально получилось. Можете ли вы предоставить мне ссылку на исходный код вашего примера проекта? Я знаю трюк с выполнением обработчика завершения в другой очереди, но вы не можете сделать это с eventStore.requestAccessToEntityType. - person mixel; 19.10.2015
comment
Вот образец проекта по вашему запросу: dropbox.com/ s/uj6as25n14roh9m/eventkit%20semaphor.zip?dl=0 - person iOSX; 19.10.2015
comment
Да, это работает, потому что обработчик completion вызывается в произвольной очереди, как указано в документах developer.apple.com/library/ios/documentation/EventKit/ Произвольный означает, что его можно вызвать в основной очереди, и в этом случае он будет вечно ждать семафора. - person mixel; 19.10.2015