我应该在这里使用完成处理程序吗?如果是这样,如何最好地做到这一点?
Posted
技术标签:
【中文标题】我应该在这里使用完成处理程序吗?如果是这样,如何最好地做到这一点?【英文标题】:Should I use a completion handler here? If so, how best to do it? 【发布时间】:2018-12-09 17:34:08 【问题描述】:我刚刚在我的应用程序中发现了一个错误,使用这种方法:
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 中。创建警报时,它会传递回主视图控制器以保存在 Core Data 中。我意识到通知没有保存在 Core Data 中,因为它们不是在警报保存在 Core Data 中时创建的。有这个闭包 center.getPendingNotificationRequests() 显然需要一些时间才能返回并运行完成处理程序。
我正在使用 getPendingNotificationRequests() 方法,因为我试图确定我可以创建多少个通知(最多 64 个)。
这是我的问题:我是否应该继续使用这种异步方法并在完成处理程序期间保存一些核心数据?还是应该将保存的代码留在主视图控制器中,而我只是停止使用此异步方法,因为出于某种原因我不需要它?
谢谢
顺便说一句,这是我的 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
【问题讨论】:
【参考方案1】:如果此方法 always 异步运行并且 always 调用完成处理程序,这可能是最简单的。因此,您的调用代码会在调用此方法时将其交给一个完成处理程序,然后停止。稍后此方法将在完成处理程序上回调调用者,从而通知它我们创建了一些通知。一个可能的草图:
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)
【讨论】:
这将如何与我在 AlarmTableViewController 中的代码一起工作,它是主要的 VC,主要将警报存储到 Core Data?我是否需要停止将警报存储在我的 AlarmTableViewController 文件中的 Core Data 中,而是在此完成处理程序中执行?或者我是否应该在 AlarmTableViewController 文件中有一些公共方法保存到核心数据,该方法被称为完成方法而不是完成处理程序? 顺便说一句,我已经更新了我的问题以获取 AlarmTableViewController 中的所有相关代码。 “相关代码”均不相关。唯一重要的是你如何称呼createNotifications
,而你没有表现出来。
我在 Alarm.init() 方法的最后调用 createNotifications(),该方法对警报时间进行了一些初始化,然后格式化传递给 createNotifications() 的 dateComponents。所有处理Core Data和创建Alarm的代码都在上面的AlarmTableViewController中。以上是关于我应该在这里使用完成处理程序吗?如果是这样,如何最好地做到这一点?的主要内容,如果未能解决你的问题,请参考以下文章
如果意图的数量大于200,我们应该为所有意图使用单独的函数处理程序