等待完成处理程序完成 - Swift

9

我正在尝试检查用户通知是否已启用,如果没有启用,则希望弹出警报。因此,我有一个名为checkAvailability的函数,它检查多个内容,包括用户通知授权状态。

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,之后闭包中的代码才执行。

我尝试将整个语句 UNUserNotificationCenter.current().requestAuthorization() 放入同步调度队列中,但这并没有起作用。

另一种方法是从闭包内部返回,但我不知道如何实现。


checkAvailabilty()中将完成处理程序作为参数添加,并在requestAuthorization的完成处理程序结束时调用它。 - shallowThought
4个回答

18

不要等待,使用完成处理程序(completion handler)来方便地处理枚举(enum):

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

在 Swift 5.5 中,使用 async/await 确实会 等待

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 到底是什么意思? - Codey
1
闭包作为参数传递给函数,并在函数返回后被调用,这意味着该闭包是逃逸的。 - vadian
当带有@escaping参数的闭包被调用时,这种情况下的函数checkAvailability会自动返回吗? - Codey
@Codey 有点像。 - vadian
好的,谢谢。我已经更多地了解了转义和noescape,并且认为我理解了它。但是我不明白的是,为什么需要将此闭包标记为escaping? - Codey

5

是的,正如你所想象的那样,这里发生的情况是函数在完成处理程序被调用之前就已经返回了。因此,你需要向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修改。 - Codey

0
另一种选择是在完成处理程序中返回两个参数:
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)
        }
    }
}

0

代码块

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) 是异步的。


谢谢你的回答,但这并不能解决我的问题,因为我必须返回一个布尔值。这就是为什么我选择了另一种解决方案。 - Codey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接