Я пытаюсь сохранить строки как свойства определенных объектов, и эти объекты используют отношения.Вот модель ...
У меня есть сущность, и она управляется подклассом NSManagedObject, который называется AlarmMO.Вот как это выглядит:
@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 естьtificationUuidChildren, который представляет собой набор объектов NotificationUuidMO.NotificationUuid выглядит следующим образом:
@objc(NotificationUuidMO)
public class NotificationUuidMO: AlarmMO {
@NSManaged public var notificationUuid: String
@NSManaged public var alarmParent: AlarmMO
}
NotificationUuidMO является подклассом AlarmMO, поэтому у каждого NotificationUuidMO есть один родительский объект AlarmMO, а объекты AlarmMO имеют возможность иметь объекты NotificationUuidMO в качестве «дочерних элементов».Вы понимаете, оператор камеры?
Вот почти все основные классы AlarmTableViewController, которые используют материал Core Data:
//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]
...
}
Как вы можете видеть, у каждого будильника есть messagesUuids, которые соответствуют уведомлениям, за которые отвечает будильник.
ЗДЕСЬ МОЯ ПРОБЛЕМА: я могу сохранить ОДИН объект NotificationUuidMO в наборе объектов NotificationUuidMO, который является messagesUuidChildren объекта AlarmMO, но не более одного.По какой-то причине, когда я создаю сигнал тревоги со значением повторения .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 () и просто попытаться создать уведомления, если смогу, и мне все равно, сколько возможных уведомлений у меня осталось создать?Или я должен как-то сохранить данные в ядре внутри этого обработчика завершения?