试图理解swift中的异步调用行为和主队列

时间:2016-07-26 10:00:53

标签: ios swift asynchronous firebase dispatch-async

我有一个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?()
})

完成块只是调用此函数。只需隐藏 popUp 视图,淡出 模糊效果删除父视图

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时,我可以看到控制台中的错误为nil。但 popUp 视图仅在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 completion可选;一个块,当它被调用   请求完成。调用               将来在主线程上异步。

我不明白的是,为什么PopUp在首先出现一定的延迟后就消失了?dispatch_assync是如何做到这一点的。{/ p>

1 个答案:

答案 0 :(得分:1)

要为视图设置动画,您应该使用主队列,并且 sendPasswordResetWithEmail 不会在主队列中调用您的块,并强调:将来在主线程上异步调用。

dispatch_async 会将执行块排入线程队列,但不保证此线程是主线程或优先级较低,例如 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)) ){} ,所以我猜 sendPasswordResetWithEmail 实现其方法使用DISPATCH_QUEUE_PRIORITY_DEFAULT或其他低优先级,这使得您的代码无法保证立即执行,实际上是执行&#39 ;时间没有准确确认。下面是一个测试,标签的动画有时会在很多秒后执行,有时候不会执行。

注意:处理或设置UI必须在主线程中,这只是用于测试。

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

因此,您应该执行在主队列(主线程)中扩展到UI的所有代码。

希望答案可以帮到你。这是GCD's guide,它会仔细而完整地解释。