пытаясь понять поведение асинхронного вызова и основную очередь в swift

У меня есть класс Authenticator с методом sendEmailForPasswordRecovery, реализующим вызов asynchronous, который отправляет электронное письмо пользователю firebase для восстановления пароля.

func sendEmailForPasswordRecovery(email: String, completion: CallBackWithError) {
        FIRAuth.auth()?.sendPasswordResetWithEmail(email, completion: { (error: NSError?) in
            completion(error)
        })
    }

Я звоню этому function из UIViewController

Authenticator().sendEmailForPasswordRecovery(email, completion: { (error: NSError?) in
      print("operation completed. error: \(error)")
      self.completion?()
})

Блоки завершения просто вызывают эту функцию. Он просто скрывает представление всплывающее окно, исчезает эффект blurEffect и удаляет его из его родительский вид.

func removeForgotPasswordScreen() {
        UIView.animateWithDuration(0.5, animations: { 
            self.blurEffectView.alpha = 0
            self.containerForEmail.alpha = 0
        }) { (_: Bool) in
            self.containerForEmail.hidden = true
            self.blurEffectView.removeFromSuperview()
        }
    }

но когда выполняется Authenticator().sendEmailForPasswordRecovery, я вижу, что в консоли ошибка равна нулю. Но всплывающее окно исчезает только через 40-50 секунд. Но когда я заключаю завершение в dispatch_async, я сразу получаю результат.

Authenticator().sendEmailForPasswordRecovery(email, completion: { (error: NSError?) in
   //   self.completion?() <----- this was causing delay

   dispatch_async(dispatch_get_main_queue(), {
          self.completion?() <------ Now it updates immidiately
    })
})

Firebase sendPasswordResetWithEmail имеет подпись:

public func sendPasswordResetWithEmail(email: String, completion: FIRSendPasswordResetCallback?)

и это говорит

завершение @param Необязательно; блок, который вызывается после завершения запроса. Вызывается асинхронно в основном потоке в будущем.

Чего я не понимаю, так это почему всплывающее окно исчезало после определенной задержки в первую очередь и как dispatch_assync сделал свою работу немедленно.


person Ccr    schedule 26.07.2016    source источник
comment
быстрое предложение - я рекомендую прочитать о Grand Central Dispatch (GCD), системе для работы с параллелизмом в разработке iOS.   -  person Matt Le Fleur    schedule 26.07.2016
comment
Когда вы устанавливаете переменную завершения - вы уверены, что она установлена ​​​​правильно в первом случае? Добавьте точку останова в removeForgotPasswordScreen, чтобы увидеть, когда это на самом деле вызывается и из какого потока.   -  person fishinear    schedule 26.07.2016
comment
@MattLeFleur спасибо за предложение. это было чрезвычайно полезно.   -  person Ccr    schedule 26.07.2016
comment
Для других youtube.com/watch?v=JrlUEUfbC1M это видео довольно хорошо объясняет .   -  person Ccr    schedule 26.07.2016


Ответы (1)


Чтобы анимировать представления, вы должны использовать основную очередь и sendPasswordResetWithEmail не вызывать ваш блок в основной очереди, и это подчеркивает:Вызывается асинхронно в основном потоке в будущем.

dispatch_async поставит блок выполнения в очередь потока, но этот поток не обязательно является основным потоком или имеет низкий приоритет, например dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {} , поэтому я предполагаю, что sendPasswordResetWithEmail реализует свой метод с использованием DISPATCH_QUEUE_PRIORITY_DEFAULT или другого низкого приоритета, что делает невозможным гарантированное немедленное выполнение вашего кода, на самом деле время выполнения точно не подтверждено. Ниже приведен тест, анимация метки иногда выполняется через много секунд, иногда не выполняется.

Примечание: обрабатывать или устанавливать пользовательский интерфейс необходимо в основном потоке, это только для теста.

class ViewController: UIViewController {
let label = UILabel()

override func viewDidLoad() {
    super.viewDidLoad()
    self.view.backgroundColor = UIColor.whiteColor()
    self.label.frame = CGRectMake(0, 0, 100, 21)
    self.label.text = "Label"
    self.label.center = self.view.center
    self.view.addSubview(self.label)

    let button = UIButton(type: .System)
    button.setTitle("Button", forState: .Normal)
    button.frame = CGRectMake(CGRectGetMinX(self.label.frame), CGRectGetMaxY(self.label.frame) + 20, 50, 20)
    button.addTarget(self, action: NSSelectorFromString("handleButtonPressed:"), forControlEvents: .TouchUpInside)
    self.view.addSubview(button)
}

func handleButtonPressed(sender: UIButton) {
    // the block will execute not in main queue
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
        NSThread.sleepForTimeInterval(1)
        // because the queue is not main queue, the animation not execute immediatelly, even not execute.
        UIView.animateWithDuration(3, animations: { 
            var origin = self.label.frame.origin
            origin.y += 100
            self.label.frame.origin = origin
            }, completion: { (complete) in
                self.label.hidden = true
        })
    }
}
}

Но если вы перейдете в основную очередь, анимация будет выполняться в соответствии с вашим временем ожидания.

func handleButtonPressed(sender: UIButton) {
    // in main queue
    dispatch_async(dispatch_get_main_queue()) {
        NSThread.sleepForTimeInterval(1)

        UIView.animateWithDuration(3, animations: { 
            var origin = self.label.frame.origin
            origin.y += 100
            self.label.frame.origin = origin
            }, completion: { (complete) in
                self.label.hidden = true
        })
    }
}

Таким образом, вы должны выполнять весь код, относящийся к пользовательскому интерфейсу, в основной очереди (основном потоке).

Надеюсь, ответ поможет вам. Это руководство по GCD. внимательно и полностью.

person Yahoho    schedule 26.07.2016