等待完成处理程序完成 - Swift
Posted
技术标签:
【中文标题】等待完成处理程序完成 - Swift【英文标题】:Wait for completion handler to finish - Swift 【发布时间】:2017-02-15 16:52:01 【问题描述】:我正在尝试检查是否启用了 UserNotifications,如果没有,我想发出警报。所以我有一个函数checkAvailability
可以检查多项内容,包括 UserNotification 授权状态。
func checkAvailabilty() -> Bool
//
// other checking
//
var isNotificationsEnabled = false
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound], completionHandler: (granted, error) in
if granted
isNotificationsEnabled = true
else
isNotificationsEnabled = false
)
if isNotificationsEnabled
return true
else
// Throw alert: Remind user to activate notifications
return false
但是完成处理程序被调用得太晚了。该函数已经返回false
,然后执行colsure中的代码。
我尝试将整个语句 UNUserNotificationCenter.current().requestAuthorization()
放入同步调度队列中,但这不起作用。
另一种方法是从闭包内部返回,但我不知道如何实现。
【问题讨论】:
将完成处理程序作为参数添加到checkAvailabilty()
并在requestAuthorization 的完成处理程序结束时调用它。
【参考方案1】:
不要等待,使用完成处理程序,方便使用枚举:
enum AuthResult
case success(Bool), failure(Error)
func checkAvailabilty(completion: @escaping (AuthResult) -> ())
//
// other checking
//
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound], completionHandler: (granted, error) in
if error != nil
completion(.failure(error!))
else
completion(.success(granted))
)
并称它为:
checkAvailabilty result in
switch result
case .success(let granted) :
if granted
print("access is granted")
else
print("access is denied")
case .failure(let error): print(error)
在带有 async/await 的 Swift 5.5 中,它确实等待
func checkAvailabilty() async throws -> Bool
//
// other checking
//
return try await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound])
并称它为:
Task
do
let granted = try await checkAvailabilty()
if granted
print("access is granted")
else
print("access is denied")
catch
print(error)
【讨论】:
感谢完美!但有一个问题:@escaping
到底是什么意思?
闭包作为参数传递给函数并在函数返回后调用,这意味着闭包正在转义
所以当一个带有@escaping
参数的闭包被调用时,这个例子中的函数checkAvailability
会自动返回?
@Codey 排序的。
好的,谢谢,我已经阅读了更多关于转义和 noescape 的内容,并认为我理解了。但是我不明白的是为什么要把这个闭包标记为转义?【参考方案2】:
是的。因此,正如您认为这里发生的事情是函数在完成处理程序被调用之前返回。因此,您要做的是将异步回调传递给 checkAvailability
函数,以便在触发完成处理程序后回调。
func checkAvailability(callback: @escaping (Bool) -> Void)
//
// other checking
//
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound], completionHandler: (granted, error) in
if granted
callback(true)
else
callback(false)
)
你可以这样调用这个函数...
checkAvailability(callback: (isAvailable) -> Void in
if isAvailable
// notifications are available
else
// present alert
)
请记住,当您呈现警报时,您可能需要显式地将调用分派给主线程,因为完成处理程序可能会在不同的线程上回调。在这种情况下,这就是您想要调用函数并显示警报的方式...
checkAvailability(callback: (isAvailable) -> Void in
if isAvailable
// notifications are available
else
DispatchQueue.main.async
// present alert
)
【讨论】:
感谢您的回答和建议在主线程中使用DispatchQueue.main.async...
明确修改 UI【参考方案3】:
代码块
if isNotificationsEnabled
return true
else
// Throw alert: Remind user to activate notifications
return false
在调用requestAuthorization(options:completionHandler)
后立即被调用。
您应该改为在完成处理程序中显示警报:
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound], completionHandler: (granted, error) in
if !granted
// Show alert
)
您的函数 checkAvailability
不再同步返回 Bool,因为对 requestAuthorization(options:completionHandler)
的调用是异步的。
【讨论】:
感谢您的回答,但这并不能解决我必须返回布尔值的问题。这就是我选择其他解决方案的原因。【参考方案4】:另一种选择是在完成处理程序中返回两个参数:
func checkAvailabilty(completion: @escaping (_ granted: Bool, _ error: Error?) -> ())
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) granted, error in
completion(granted, error)
用法
checkAvailabilty granted, error in
guard error == nil else
// An Authorization error has occurred. Present an alert to the user with the error description.
DispatchQueue.main.async
let alert = UIAlertController(title: "Alert", message: error?.localizedDescription ?? "Authorization failed. Unknown error.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alert, animated: true)
return
if granted
print("granted") // authorization was successful
else
print("denied") // present alert from the main thread
DispatchQueue.main.async
let alert = UIAlertController(title: "Attention", message: "The App needs you to turn on notifications !!!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alert, animated: true)
【讨论】:
以上是关于等待完成处理程序完成 - Swift的主要内容,如果未能解决你的问题,请参考以下文章
Swift - 我如何等待 dispatch_async 完成?
当完成处理程序显式使用 @escaping 时,Swift 推断完成处理程序闭包是默认的 @nonescaping 而不是 @escaping