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

trying to understand the asynchronous call behaviour and main queue in swift

我有一个 Authenticator class 有 sendEmailForPasswordRecovery 方法实现 asynchronous 调用,将电子邮件发送给 firebase 用户以恢复密码。

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

我从 UIViewController

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

完成块只是调用这个函数。它只是 隐藏 一个 popUp 视图,淡出 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 时,我可以看到控制台中的错误为 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 Optionally; a block which is invoked when the request finishes. Invoked asynchronously on the main thread in the future.

我不明白的是,为什么 PopUp 在经过一定的延迟后会消失,dispatch_assync 是如何立即完成工作的。

要使视图动画化,您应该使用主队列,并且sendPasswordResetWithEmail不要在主队列中调用您的块,它强调:在主队列上异步调用以后的话题。

dispatch_async会将execute block入队到线程的队列中,但是这个线程不保证是主线程或者是低优先级,比如dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {}, 所以我猜想 sendPasswordResetWithEmail 实现了它的方法使用DISPATCH_QUEUE_PRIORITY_DEFAULT 或其他低优先级,这使得你的代码不能保证立即执行,实际上执行的时间并不能准确确定。下面是一个测试,标签的动画有时会在很多秒后执行,有时不执行。

注意: handle or set 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,解释的很仔细,很完整