等待完成处理程序完成 - 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 完成?

等待函数完成处理程序完成失败

您如何等待主队列完成处理程序?

在执行代码之前等待 Swift 动画完成

Swift 中的完成处理程序错误

当完成处理程序显式使用 @escaping 时,Swift 推断完成处理程序闭包是默认的 @nonescaping 而不是 @escaping