我正在尝试将字符串另存为某些实体的属性,并且那些实体正在使用关系。这是模型...
我有一个实体,由一个称为AlarmMO的NSManagedObject子类管理。看起来像这样:
@objc(AlarmMO)
public class AlarmMO: NSManagedObject {
@NSManaged public var alarmNumber: Int64
@NSManaged public var alarmTime: NSDate?
@NSManaged public var endTimeInterval: Double
@NSManaged public var recurrence: Int64
@NSManaged public var note: String?
@NSManaged public var startTimeInterval: Double
@NSManaged public var notificationUuidChildren: Set<NotificationUuidMO>?
}
如您所见,AlarmMO对象具有NotificationUuidChildren,它是一组NotificationUuidMO对象。 NotificationUuid看起来像这样:
@objc(NotificationUuidMO)
public class NotificationUuidMO: AlarmMO {
@NSManaged public var notificationUuid: String
@NSManaged public var alarmParent: AlarmMO
}
NotificationUuidMO是AlarmMO的子类,因此每个NotificationUuidMO都有一个AlarmMO父级,并且AlarmMO对象可以选择将NotificationUuidMO对象作为“子级”。你开始这样的,相机家伙?
这里几乎是所有使用Core Data的主要AlarmTableViewController类:
//MARK: 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.notificationUuidChildren?.count ?? 0, 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)
resetAlarmNumbers()
for alarm in self.alarms {
os_log("There are %d notifications for alarm %d", log: OSLog.default, type: .debug, alarm.notificationUuidChildren?.count ?? 0, alarm.alarmNumber)
}
os_log("----- notificationUuids: -----", log: OSLog.default, type: .debug)
if let notificationUuids = self.getNotificationUuidsFromAlarmMO(alarmMO: alarmMO) {
for uuid in notificationUuids {
os_log("uuid: %@", log: OSLog.default, type: .debug, uuid)
}
} else {
os_log("There are no notifications for the provided AlarmMO in tableView(cellForRowAt:)", log: OSLog.default, type: .debug)
return cell
}
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
}
if let notificationUuids = self.getNotificationUuidsFromAlarmMO(alarmMO: alarm) {
self.removeNotifications(notificationUuids: notificationUuids)
} else {
os_log("There are no notifications for the provided AlarmMO in tableView(forRowAt:)", log: OSLog.default, type: .debug)
}
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
managedContext.delete(alarm)
self.alarms.remove(at: indexPath.row)
resetAlarmNumbers()
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)
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 resetAlarmNumbers() {
for (index, alarm) in self.alarms.enumerated() {
let alarmNumber = index + 1
alarm.setValue(alarmNumber, forKey: "alarmNumber")
}
}
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 {
if let notificationUuids = self.getNotificationUuidsFromAlarmMO(alarmMO: alarmMOToDelete) {
self.removeNotifications(notificationUuids: notificationUuids)
} else {
os_log("There are no notifications for the provided AlarmMO in deleteOldAlarms()", log: OSLog.default, type: .debug)
return
}
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("There are no notifications for the provided AlarmMO in calculateAlarmsToDelete()", 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 formatTime(hour: Int, minute: Int) -> String {
let amPm = formatAmPm(hour: hour)
let hourStr = formatHour(hour: hour)
let minuteStr = formatMinute(minute: minute)
return hourStr + ":" + minuteStr + amPm
}
private func formatAmPm(hour: Int) -> String {
if hour < 13 {
return "am"
} else {
return "pm"
}
}
private func formatHour(hour: Int) -> String {
if hour == 0 {
return "12"
} else if hour > 12 {
return String(hour - 12)
} else {
return String(hour)
}
}
private func formatMinute(minute: Int) -> String {
if minute < 10 {
return "0" + String(minute)
} else {
return String(minute)
}
}
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.note, forKeyPath: "note")
for notificationUuid in alarmToSave.notificationUuids {
let entity = NSEntityDescription.entity(forEntityName: "NotificationUuid", in: managedContext)!
let notificationUuidMO = NotificationUuidMO(entity: entity, insertInto: managedContext)
notificationUuidMO.notificationUuid = notificationUuid
notificationUuidMO.alarmParent = alarmMO
alarmMO.addToNotificationUuidChildren(notificationUuidMO)
}
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 notificationUuidChildren = alarmMO.notificationUuidChildren else {
os_log("Returned no notificationUuidChildren in getNotificationUuidsFromAlarmMO() in AlarmTableViewController.swift", log: OSLog.default, type: .debug)
return nil
}
var notificationUuids = [String]()
for notificationUuidMO in notificationUuidChildren {
notificationUuids.append(notificationUuidMO.notificationUuid)
}
os_log("Returning %d notificationUuids in getNotificationUuidsFromAlarmMO() in AlarmTableViewController.swift", log: OSLog.default, type: .debug, notificationUuids.count)
return notificationUuids
}
要查看的主要部分是viewDidLoad(),tableView数据源方法,deleteOldAlarms(),loadAlarms(),saveAlarm()和getNotificationsFromAlarmMO()。这是Alarm通常从公共级别定义的:
class Alarm {
//MARK: Properties
var alarmTime: Date?
var alarmNumber: Int
var alarmIntervalBeginTimeDouble: Double
var alarmIntervalEndTimeDouble: Double
var note: String
var recurrence: RecurrenceOptions
var notificationUuids: [String]
...
}
如您所见,每个警报都有与警报负责的通知相对应的notificationUuid。
这是我的问题:我可以将一个NotificationUuidMO对象保存到一组NotificationUuidMO对象中,该对象是AlarmMO对象的notificationUuidChildren,但不能超过一个。出于某种原因,当我创建一个重复值为.daily的警报时,该警报创建了多个通知,没有任何通知会保存为警报下的NotificationUuidMO对象。而且由于deleteOldAlarms()会删除没有安排任何通知的警报,因此这些警报在创建后会立即删除。不酷。
什么可能导致此行为?
重要编辑: 我现在从下面的注释中意识到,Alarm.swift调用了notificationCenter.getPendingNotificationRequests(),这是一个异步方法,并带有完成处理程序。当我返回到另一个视图控制器,并且将警报保存到核心数据之后,我将添加所有通知。
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
}
}
}
}
该如何处理?我应该摆脱对getPendingNotificationRequests()的调用,如果可以的话,只尝试创建通知,而不在乎我还有多少个通知要创建?还是应该以某种方式保存到此完成处理程序中的核心数据?