ios - 我应该在这里使用完成处理程序吗?如果是这样,如何最好地做到这一点?

标签 ios swift closures completionhandler

我刚刚在这个方法中发现我的应用程序中有一个错误:

private func createNotifications(dateComponents: DateComponents) {

    switch (recurrence) {
    case .today:
        createNotification(for: dateComponents)
    case .tomorrow:
        createNotification(for: day(after: dateComponents))
    case .daily:
        let center = UNUserNotificationCenter.current()
        center.getPendingNotificationRequests { (notifications) in
            var numberOfCreatableNotifications = 64 - notifications.count
            var numberOfCreatedNotifications = 0
            var currentDay: DateComponents? = dateComponents
            while numberOfCreatableNotifications > 0
                    && numberOfCreatedNotifications < self.NUMBER_OF_ALLOWED_NOTIFICATIONS_CREATED_AT_ONE_TIME {
                self.createNotification(for: currentDay)
                currentDay = self.day(after: currentDay)
                numberOfCreatableNotifications -= 1
                numberOfCreatedNotifications += 1
            }
        }
    }
}

我正在尝试创建一个具有特定重复选项(今天、明天或每天)的警报,但每日案例不起作用。上面的代码在 Alarm.swift 中。创建警报后,它会传回主视图 Controller 以保存在核心数据中。我意识到通知没有保存在核心数据中,因为它们不是在警报保存在核心数据中时创建的。有一个闭包 center.getPendingNotificationRequests() 显然需要一些时间才能返回并运行完成处理程序。

我正在使用 getPendingNotificationRequests() 方法,因为我试图确定我还可以创建多少条通知(最多 64 条)。

这是我的问题:我是否应该继续使用这种异步方法并在完成处理程序期间只在核心数据中做一些保存?还是应该将用于保存的代码保留在主视图 Controller 中,而我只是停止使用此异步方法,因为出于某种原因我不需要它?

谢谢

顺便说一句,这是我的 AlarmTableViewController 文件中的大部分代码,我的应用程序中的主要 VC:

//MARK: Public properties
var alarms = [AlarmMO]()
let ALARM_CELL_IDENTIFIER = "AlarmTableViewCell"

override func viewDidLoad() {
    super.viewDidLoad()
    requestUserNotificationsPermissionsIfNeeded()
    NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
    loadAlarms()

    for alarm in self.alarms {
        os_log("There are %d notifications for alarm %d", log: OSLog.default, type: .debug, alarm.notificationUuids.count, alarm.alarmNumber)
    }
}

deinit {
    NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
}

// MARK: - Table view data source

override func numberOfSections(in tableView: UITableView) -> Int {
    return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return self.alarms.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    guard let cell = tableView.dequeueReusableCell(withIdentifier: ALARM_CELL_IDENTIFIER, for: indexPath) as? AlarmTableViewCell else {
        fatalError("The dequeued cell is not an instance of AlarmTableViewCell.")
    }

    guard let alarmMO = self.alarms[safe: indexPath.row] else {
        os_log("Could not unwrap alarm for indexPath in AlarmTableViewController.swift", log: OSLog.default, type: .default)
        self.tableView.reloadData()
        return AlarmTableViewCell()
    }
    let alarmNumber = alarmMO.value(forKey: "alarmNumber") as! Int
    let beginTime = alarmMO.value(forKey: "startTimeInterval") as! Double
    let endTime = alarmMO.value(forKey: "endTimeInterval") as! Double
    cell.alarmNumberLabel.text = "Alarm " + String(alarmNumber)

    let beginTimeHour = Alarm.extractHourFromTimeDouble(alarmTimeDouble: beginTime)
    let beginTimeMinute = Alarm.extractMinuteFromTimeDouble(alarmTimeDouble: beginTime)
    cell.beginTimeLabel.text = formatTime(hour: beginTimeHour, minute: beginTimeMinute)

    let endTimeHour = Alarm.extractHourFromTimeDouble(alarmTimeDouble: endTime)
    let endTimeMinute = Alarm.extractMinuteFromTimeDouble(alarmTimeDouble: endTime)
    cell.endTimeLabel.text = formatTime(hour: endTimeHour, minute: endTimeMinute)

    guard let notificationUuids = self.getNotificationUuidsFromAlarmMO(alarmMO: alarmMO) else {
        os_log("Could not get notificationUuids from AlarmMO in tableView(cellForRowAt:) in AlarmTableViewController.swift", log: OSLog.default, type: .debug)
        return cell
    }
    os_log("----- notificationUuids: -----", log: OSLog.default, type: .debug)
    for uuid in notificationUuids {
        os_log("uuid: %@", log: OSLog.default, type: .debug, uuid)
    }

    return cell

}

override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
    return true
}

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {

    if (editingStyle == .delete) {

        guard let alarm = self.alarms[safe: indexPath.row] else {
            os_log("Could not get alarm from its indexPath in AlarmTableViewController.swift", log: OSLog.default, type: .default)
            self.tableView.reloadData()
            return
        }
        guard let notificationUuids = self.getNotificationUuidsFromAlarmMO(alarmMO: alarm) else {
            os_log("Could not get notificationUuids from AlarmMO in tableView(forRowAt:) in AlarmTableViewController.swift", log: OSLog.default, type: .debug)
            return
        }
        self.removeNotifications(notificationUuids: notificationUuids)
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
            return
        }
        let managedContext = appDelegate.persistentContainer.viewContext
        managedContext.delete(alarm)
        self.alarms.remove(at: indexPath.row)

        for (index, alarm) in self.alarms.enumerated() {
            let alarmNumber = index + 1
            alarm.setValue(alarmNumber, forKey: "alarmNumber")
        }

        self.saveContext()
        self.tableView.reloadData()

    }

}

// MARK: Actions

@IBAction func unwindToAlarmList(sender: UIStoryboardSegue) {

    if let sourceViewController = sender.source as? AddAlarmViewController, let alarm = sourceViewController.alarm {
        let newIndexPath = IndexPath(row: self.alarms.count, section: 0)
        os_log("There are %d notificationUuids attached to the alarm created", log: OSLog.default, type: .debug, alarm.notificationUuids.count)
        saveAlarm(alarmToSave: alarm)
        tableView.insertRows(at: [newIndexPath], with: .automatic)
    }

}

// MARK: Private functions

@objc private func didBecomeActive() {

    deleteOldAlarms {
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }

}

private func deleteOldAlarms(completionHandler: @escaping () -> Void) {

    os_log("deleteOldAlarms() called", log: OSLog.default, type: .default)
    let notificationCenter = UNUserNotificationCenter.current()
    var alarmsToDelete = [AlarmMO]()
    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
        return
    }
    let managedContext = appDelegate.persistentContainer.viewContext

    notificationCenter.getPendingNotificationRequests(completionHandler: { (requests) in

        alarmsToDelete = self.calculateAlarmsToDelete(requests: requests)
        os_log("Deleting %d alarms", log: OSLog.default, type: .debug, alarmsToDelete.count)
        for alarmMOToDelete in alarmsToDelete {
            guard let notificationUuids = self.getNotificationUuidsFromAlarmMO(alarmMO: alarmMOToDelete) else {
                os_log("Could not get notificationUuids from AlarmMO in deleteOldAlarms() in AlarmTableViewController.swift", log: OSLog.default, type: .debug)
                return
            }
            self.removeNotifications(notificationUuids: notificationUuids)
            managedContext.delete(alarmMOToDelete)
            self.alarms.removeAll { (alarmMO) -> Bool in
                return alarmMOToDelete == alarmMO
            }
        }
        completionHandler()

    })

}

private func calculateAlarmsToDelete(requests: [UNNotificationRequest]) -> [AlarmMO] {
    var activeNotificationUuids = [String]()
    var alarmsToDelete = [AlarmMO]()
    for request in requests {
        activeNotificationUuids.append(request.identifier)
    }
    for alarm in self.alarms {
        guard let notificationUuids = self.getNotificationUuidsFromAlarmMO(alarmMO: alarm) else {
            os_log("Could not get notificationUuids from AlarmMO in deleteOldAlarms() in AlarmTableViewController.swift", log: OSLog.default, type: .debug)
            return []
        }
        let activeNotificationUuidsSet: Set<String> = Set(activeNotificationUuids)
        let alarmUuidsSet: Set<String> = Set(notificationUuids)
        let union = activeNotificationUuidsSet.intersection(alarmUuidsSet)
        if union.isEmpty {
            alarmsToDelete.append(alarm)
        }
    }
    return alarmsToDelete
}

private func removeNotifications(notificationUuids: [String]) {

    os_log("Removing %d alarm notifications", log: OSLog.default, type: .debug, notificationUuids.count)
    let notificationCenter = UNUserNotificationCenter.current()
    notificationCenter.removePendingNotificationRequests(withIdentifiers: notificationUuids)

}

private func loadAlarms() {

    os_log("loadAlarms() called", log: OSLog.default, type: .debug)
    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
            return
    }
    let managedContext = appDelegate.persistentContainer.viewContext
    let fetchRequest = NSFetchRequest<AlarmMO>(entityName: "Alarm")

    do {
        if self.alarms.count == 0 {
            self.alarms = try managedContext.fetch(fetchRequest)
            os_log("Loading %d alarms", log: OSLog.default, type: .debug, self.alarms.count)
        } else {
            os_log("Didn't need to load alarms", log: OSLog.default, type: .debug)
        }
    } catch let error as NSError {
        print("Could not fetch alarms. \(error), \(error.userInfo)")
    }

}

private func saveAlarm(alarmToSave: Alarm) {

    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
            return
    }
    let managedContext = appDelegate.persistentContainer.viewContext
    let entity = NSEntityDescription.entity(forEntityName: "Alarm", in: managedContext)!
    let alarmMO = AlarmMO(entity: entity, insertInto: managedContext)

    alarmMO.setValue(alarmToSave.alarmTime, forKeyPath: "alarmTime")
    alarmMO.setValue(alarmToSave.alarmNumber, forKeyPath: "alarmNumber")
    alarmMO.setValue(alarmToSave.alarmIntervalBeginTimeDouble, forKeyPath: "startTimeInterval")
    alarmMO.setValue(alarmToSave.alarmIntervalEndTimeDouble, forKeyPath: "endTimeInterval")
    alarmMO.setValue(alarmToSave.recurrence.hashValue, forKeyPath: "recurrence")
    alarmMO.setValue(alarmToSave.notificationUuids, forKeyPath: "notificationUuids")

    if managedContext.hasChanges {
        do {
            try managedContext.save()
            self.alarms.append(alarmMO)
        } catch let error as NSError {
            print("Could not save alarm to CoreData. \(error), \(error.userInfo)")
        }
    } else {
        os_log("No changes to the context to save", log: OSLog.default, type: .debug)
    }

}

private func getNotificationUuidsFromAlarmMO(alarmMO: AlarmMO) -> [String]? {

    guard let notificationUuids = alarmMO.value(forKey: "notificationUuids") as! [String]? else {
        os_log("Found nil when attempting to unwrap notificationUuids in getNotificationUuidsFromAlarmMO() in AlarmTableViewController.swift, returning nil",
               log: OSLog.default, type: .default)
        return nil
    }
    return notificationUuids

}

最佳答案

如果此方法始终 异步运行并且始终 调用完成处理程序,这可能是最简单的。因此,您的调用代码会在调用此方法时将其交给一个完成处理程序,然后停止。稍后此方法将在完成处理程序上回调调用者,从而通知它我们创建了一些通知。一个可能的草图:

private func createNotifications(dateComponents: DateComponents, completion: (Int) -> Void) {
    let center = UNUserNotificationCenter.current()
    center.getPendingNotificationRequests { (notifications) in
        switch (recurrence) { 
            // this is wrong, you should not be looking outside this method for the recurrence value...
            // ... but it's just a sketch
            case .today:
                createNotification(for: dateComponents)
                completion(1)
            case .tomorrow:
                createNotification(for: day(after: dateComponents))
                completion(1)
            case .daily:
                // ... decide whether to create the notifications or not ...
                // ... and create however many you decide to ...
                let howManyWeCreated = // however many we created
                completion(howManyWeCreated)
        }
    }
}

关于ios - 我应该在这里使用完成处理程序吗?如果是这样,如何最好地做到这一点?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53694887/

相关文章:

arrays - SWIFT OS X - 闭包语句内的多个语句,调试工具?

带有不同大小图标的 iOS 自定义 tabview 栏

ios - Xcode 7 : linker command failed with exit code 1 (use -v to see invocation) again

iphone - cocos2d CCSprite 碰撞问题

ios - Xcode 7.1 中没有 "Run in Full Simulator"的 UIView 动画?

json - Swift - 解析 - 如何循环进入 PFObject 属性

android - 使用 Cordova-plugin-facebook4 邀请 Facebook App

javascript - 闭包中变量引用的意外行为

data-binding - 如何在 riot js 中进行变量更新

javascript - 说明为什么我的 JS 闭包没有正确返回?