简体   繁体   English

等待异步代码完成Swift

[英]Wait for async code to finish Swift

I've been working on one of my project where I allow users to schedule multiple notifications at their desired time. 我一直在从事我的项目之一,在该项目中,我允许用户在所需的时间安排多个通知。 I'm using the new UserNotifications in iOS 10. 我正在iOS 10中使用新的UserNotifications

In order for all notifications to be scheduled properly each notification needs to have its own unique identifier. 为了正确安排所有通知,每个通知都必须具有自己的唯一标识符。 I composed mine according to my data models : 我根据我的数据模型组成了我的:

  1. The id of my model 我的模特编号
  2. A number that increments each time a new notification is created 每次创建新通知时递增的数字
  3. The above is separated by an underscore 上面用下划线隔开

So for example if I had to schedule 15 notifications for the object with the id 3 it would look like this : 3_1, 3_2, 3_3...3_15 因此,例如,如果我必须为ID为3的对象安排15条通知,则其外观应为: 3_1, 3_2, 3_3...3_15

Here is how I did it : 这是我的做法:

@available(iOS 10.0, *)
    func checkDeliveredAndPendingNotifications(completionHandler: @escaping (_ identifierDictionary: Dictionary<String, Int>) -> ()) {

        var identifierDictionary:[String: Int] = [:]
        UNUserNotificationCenter.current().getDeliveredNotifications { (notifications) in

            for notification in notifications {
                let identifierArraySplit = notification.request.identifier.components(separatedBy: "_")
                if identifierDictionary[identifierArraySplit[0]] == nil || identifierDictionary[identifierArraySplit[0]]! < Int(identifierArraySplit[1])!  {
                    identifierDictionary[identifierArraySplit[0]] = Int(identifierArraySplit[1])
                }
            }

            UNUserNotificationCenter.current().getPendingNotificationRequests(completionHandler: { (requests) in
                for request in requests {
                    let identifierArraySplit = request.identifier.components(separatedBy: "_")
                    if identifierDictionary[identifierArraySplit[0]] == nil || Int(identifierArraySplit[1])! > identifierDictionary[identifierArraySplit[0]]!  {
                        identifierDictionary[identifierArraySplit[0]] = Int(identifierArraySplit[1])
                    }
                }
                completionHandler(identifierDictionary)
            })
        }
    }


@available(iOS 10.0, *) 
    func generateNotifications() {
        for medecine in medecines {
            self.checkDeliveredAndPendingNotifications(completionHandler: { (identifierDictionary) in
                DispatchQueue.main.async {
                    self.createNotification(medecineName: medecine.name, medecineId: medecine.id, identifierDictionary: identifierDictionary)
                    }                    
            })
        }
    }


@available(iOS 10.0, *)
    func createNotification(medecineName: String, medecineId: Int identifierDictionary: Dictionary<String, Int>) {

        let takeMedecineAction = UNNotificationAction(identifier: "TAKE", title: "Take your medecine", options: [.destructive])
        let category = UNNotificationCategory(identifier: "message", actions: [takeMedecineAction], intentIdentifiers:[], options: [])
        UNUserNotificationCenter.current().setNotificationCategories([category])

        let takeMedecineContent = UNMutableNotificationContent()
        takeMedecineContent.userInfo = ["id": medecineId]
        takeMedecineContent.categoryIdentifier = "message"
        takeMedecineContent.title = medecineName
        takeMedecineContent.body = "It's time for your medecine"
        takeMedecineContent.badge = 1
        takeMedecineContent.sound = UNNotificationSound.default()

        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: false)

        var takeMedecineIdentifier = ""
        for identifier in identifierDictionary {
            if Int(identifier.key) == medecineId {
                let nextIdentifierValue = identifier.value + 1
                takeMedecineIdentifier = String(medecineId) + "_" + String(nextIdentifierValue)
            }
        }
        let takeMedecineRequest = UNNotificationRequest(identifier: takeMedecineIdentifier, content: takeMedecineContent, trigger: trigger)

        UNUserNotificationCenter.current().add(takeMedecineRequest, withCompletionHandler: { (error) in
            if let _ = error {
                print("There was an error : \(error)")
            }
        })
    }

The checkDeliveredAndPendingNotifications makes sure that later on I'll create identifiers that do not exist already. checkDeliveredAndPendingNotifications确保以后我将创建尚不存在的标识符。

When it has finished doing its job, I call the createNotification which uses the dictionary returned by the previous function to generate a proper identifier. 完成工作后,我调用createNotification ,它使用前一个函数返回的字典来生成适当的标识符。

For example if there were 5 notifications delivered on disk and 10 others waiting for the model with the id 3 it would look like this : 例如,如果在磁盘上传递了5条通知,而其他10条正在等待ID为3的模型,则它将如下所示:

["3" : 15]

The createNotification is simply going to take the value in the dictionary and increments it by 1 to create the identifier. createNotification只是将要在字典中获取值并将其递增1以创建标识符。

The real problem comes with : 真正的问题是:

UNUserNotificationCenter.current().add(takeMedecineRequest, withCompletionHandler: { (error) in
            if let _ = error {
                print("There was an error : \(error)")
            }
        })

It is an async task. 这是一个异步任务。 Considering it does not wait, as soon as I get back to my loop in the generateNotifications the checkDeliveredAndPendingNotifications does not return a correct dictionary because the notification didn't finish creating. 考虑到它不会等待,一旦我回到generateNotifications的循环,由于通知未完成创建,因此checkDeliveredAndPendingNotifications不会返回正确的字典。

Considering the example above if I had to schedule 3 notifications I'd like to get something like this: 考虑上面的示例,如果我必须安排3条通知,我想获得以下信息:

print("identifierDictionary -> \(identifierDictionary)") // ["3":15], ["3":16], ["3":17]
print("unique identifier created -> \(takeMedecineIdentifier") // 3_16, 3_17, 3_18

But right now I'm getting : 但是现在我得到:

print("identifierDictionary -> \(identifierDictionary)") // ["3":15], ["3":15], ["3":15]
print("unique identifier created -> \(takeMedecineIdentifier") // 3_16, 3_16, 3_16

So, how can I wait for this async call to finish before getting back to my loop? 因此,如何回到异步循环之前等待异步调用完成?

Thanks in advance for your help. 在此先感谢您的帮助。

If you do not need to be able to 'read' from the identifier which notification it is, you could used a randomized string as identifier instead. 如果不需要从标识符“读取”到哪个通知,则可以使用随机字符串作为标识符。

Even if it is possible to properly generate a unique id like you do now you should not rely on the control flow for correct id generation. 即使像现在一样可以正确地生成唯一的ID,也不应依赖于控制流来正确生成ID。 This is generally considered bad coding practice, especially when relying on (3th) party libraries or API's. 通常认为这是不良的编码做法,尤其是在依赖(第3个)第三方库或API的情况下。 One change could break it. 一种更改可能会破坏它。

You could generate randomized strings as described here . 您可以按照此处所述生成随机字符串。 Using a alphanumeric string of 24 characters gives (36+36+10)^24 combinations, making the chance of a collision negligable. 使用包含24个字符的字母数字字符串可得到(36 + 36 + 10)^ 24个组合,从而使碰撞的机会微不足道。

You can use the userinfo dictionary or some other means of persistence to associate the identifiers with specific notifications. 您可以使用userinfo词典或某些其他持久性方式将标识符与特定通知相关联。 If you are using CoreData you can associate notification objects, with unique identifiers, to medicineRequests. 如果您使用的是CoreData ,则可以将具有唯一标识符的通知对象与medicineRequests关联。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM