Завершает ли обработчик завершения функцию? - PullRequest
0 голосов
/ 31 января 2020

Возможно, я не понимаю концепцию обработчика завершения, но у меня есть такая функция:

func someFunction(completion: @escaping (Bool, LoginError?) -> Void) {
    self.checkDevice() { allowed, error in
       if let e = error {
          completion(false, e)
       }
        completion(true, nil)
    }
}

Несмотря на то, что checkDevice() делает что-то, основная предпосылка c заключается в том, что он выполняет асинхронный сетевой вызов и возвращает либо true без ошибок (nil), либо возвращает false с ошибкой.

Когда я запускаю этот код, я Я обнаружил, что обработчик завершения вызывается дважды. Он отправляет завершение в виде кортежа (как false, error), а также как (true, nil). Я провел некоторую отладку, и, кажется, нет способа, которым someFunction() вызывается дважды.

Я верил, что после отправки завершение функция завершится , В моем тестовом примере я выдвигаю ошибку из checkDevice(), которая должна привести к отправке завершения как (false, error), но я вижу и (false, error), и (true, nil). Разве завершение не приводит к немедленному завершению функции?

Ответы [ 3 ]

3 голосов
/ 31 января 2020

Я верил, что после отправки завершения функция завершится.

Нет, с чего бы это? Когда вы вызываете эту функцию, это похоже на вызов любой другой функции. Имя не обладает магией c.

Перепишите его так:

func someFunction(completion: @escaping (Bool, LoginError?) -> Void) {
    self.checkDevice() { allowed, error in
        if let e = error {
            completion(false, e)
            return // *
        }
        completion(true, nil)
    }
}

На самом деле, я должен отметить, что это плохой способ написания вашего обработчика завершения. Вместо того, чтобы принимать два параметра, Bool и Дополнительный LoginError, которые будут использоваться, только если Bool равен false, он должен принять один параметр - Результат, который несет в себе оба результата: успешный или неудачный, и, если мы потерпели неудачу, в чем ошибка:

func someFunction(completion: @escaping (Result<Void, Error>) -> Void) {
    self.checkDevice() { allowed, error in
        completion( Result {
            if let e = error { throw e }
        })
    }
}

Как видите, использование Result в качестве параметра позволяет вам более элегантно реагировать на то, что произошло.

Действительно, checkDevice сам может передать Result в свой обработчик завершения , и тогда все будет еще элегантнее (и проще).

1 голос
/ 31 января 2020

Рассмотрите возможность включения имен параметров (для справки). И завершение должно быть вызвано только один раз. Вы можете сделать это, используя return или используя полное условное выражение if-else.

func someFunction(completion: @escaping (_ done: Bool, _ error: LoginError?) -> Void) {

    checkDevice() { (allowed, error) in

        if let e = error {
            completion(false, e)
        } else {
            completion(true, nil)
        }

    }

}

func someFunction(completion: @escaping (_ done: Bool, _ error: LoginError?) -> Void) {

    checkDevice() { (allowed, error) in

        if let e = error {

            completion(false, e)
            return

        }

        completion(true, nil)

    }

}

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

someFunction { (done, error) in

    if let error = error {
        ...
    } else if done {
        ...
    }

}
1 голос
/ 31 января 2020

Обработчик завершения не завершает функцию. Типичный способ завершить функцию - вставить return.

См. Комментарий к: ваши данные c кейс

...