Подождите, пока асинхронный код завершит Swift

Я работаю над одним из своих проектов, где я позволяю пользователям планировать несколько уведомлений в нужное время. Я использую новый UserNotifications в iOS 10.

Для правильного планирования всех уведомлений каждое уведомление должно иметь собственный уникальный идентификатор. Я составил свой в соответствии с моими моделями данных:

  1. Идентификатор моей модели
  2. Число, которое увеличивается каждый раз, когда создается новое уведомление.
  3. Вышеупомянутое разделено подчеркиванием

Так, например, если бы мне нужно было запланировать 15 уведомлений для объекта с идентификатором 3, это выглядело бы так: 3_1, 3_2, 3_3...3_15

Вот как я это сделал:

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

checkDeliveredAndPendingNotifications гарантирует, что позже я создам идентификаторы, которых еще не существует.

Когда он закончит свою работу, я вызову createNotification, который использует словарь, возвращенный предыдущей функцией, для создания правильного идентификатора.

Например, если на диск было доставлено 5 уведомлений, а 10 других ожидают модель с идентификатором 3, это будет выглядеть так:

["3" : 15]

createNotification просто берет значение из словаря и увеличивает его на 1 для создания идентификатора.

Настоящая проблема связана с:

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

Это асинхронная задача. Учитывая, что это не ждет, как только я вернусь к своему циклу в generateNotifications, checkDeliveredAndPendingNotifications не вернет правильный словарь, потому что уведомление не закончилось создание.

Учитывая приведенный выше пример, если бы мне пришлось запланировать 3 уведомления, я бы хотел получить что-то вроде этого:

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

Но прямо сейчас я получаю:

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

Итак, как я могу дождаться завершения этого асинхронного вызова, прежде чем вернуться к моему циклу?

Заранее спасибо за помощь.


person Croisciento    schedule 13.10.2016    source источник
comment
Вам нужно иметь возможность «читать» из идентификатора, какое это уведомление? Вместо этого вы можете использовать рандомизированную строку в качестве идентификатора. Даже если это возможно, вы не должны полагаться на поток управления для правильной генерации идентификатора, одно изменение кода или API может сломать все.   -  person Thomm    schedule 13.10.2016
comment
@ Томм Да, к сожалению. Вы можете удалить ожидающие и доставленные уведомления только с помощью идентификатора. Я мог бы передать идентификатор в словаре userInfo в запросе уведомления и создать рандомизированную строку для идентификатора, но могу ли я быть абсолютно уверен, что два уведомления не будут иметь один и тот же идентификатор, перейдя по вашей ссылке? Если идентификатор не уникален, он не будет доставлять все уведомления, что приведет к поломке моего приложения.   -  person Croisciento    schedule 13.10.2016
comment
Он использует arc4random, который использует разные семена, поэтому он не генерирует одну и ту же последовательность. Использование буквенно-цифровой строки из 24 символов дает (36+36+10)^24 комбинации, что делает вероятность столкновения незначительной.   -  person Thomm    schedule 13.10.2016
comment
@ Томм, хорошо, спасибо. Я решил пойти с этим решением. Теперь я могу настроить все правильно.   -  person Croisciento    schedule 13.10.2016
comment
Отлично, я разместил это как ответ. Вы можете принять / проголосовать за то, что считаете нужным. Не стесняйтесь, если что-то непонятно.   -  person Thomm    schedule 13.10.2016


Ответы (1)


Если вам не нужно иметь возможность «читать» из идентификатора, какое это уведомление, вы можете вместо этого использовать рандомизированную строку в качестве идентификатора.

Даже если можно правильно сгенерировать уникальный идентификатор, как вы это делаете сейчас, вы не должны полагаться на поток управления для правильного создания идентификатора. Обычно это считается плохой практикой кодирования, особенно при использовании (сторонних) сторонних библиотек или API. Одно изменение может сломать его.

Вы можете создавать рандомизированные строки, как описано здесь. Использование буквенно-цифровой строки из 24 символов дает (36+36+10)^24 комбинации, что делает вероятность столкновения незначительной.

Вы можете использовать словарь userinfo или другие средства сохранения, чтобы связать идентификаторы с конкретными уведомлениями. Если вы используете CoreData, вы можете связать объекты уведомлений с уникальными идентификаторами с MedicineRequests.

person Thomm    schedule 13.10.2016