试图了解 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,解释的很仔细,很完整
我有一个 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,解释的很仔细,很完整