Лучшие практики для загрузки и обновления данных, чувствительных ко времени - PullRequest
0 голосов
/ 23 марта 2019

РЕЗЮМЕ ЗАДАЧИ

Я пишу приложение для iOS, которое похоже на гибрид между списком дел и календарем. В отличие от списка дел, вы не отметите все как завершенное. Скорее, задачи делятся на секции в UITableView для активных и неактивных. Проблема в том, что раздел и сортировка могут измениться в любое время относительно текущей даты и времени.

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

В настоящее время приложение прекрасно загружает данные ядра, и контроллер представления корректно корректирует сечение и порядок сортировки строк при добавлении, изменении или удалении записи. Я также могу обновить отдельную запись, когда ее уведомление запускается, когда мое приложение находится на переднем плане. Т.е., так как уведомление сработало, неповторяющиеся напоминания перемещаются из Активного в Неактивное, и повторяющиеся напоминания также устанавливают свой раздел и следующую дату оповещения.

Так что сейчас я больше всего борюсь с:

  1. Начальная нагрузка . Как и когда обновлять дату section / nextAlert при начальной загрузке приложения после выборки данных, но до того, как контроллер представления начнет их отображать.

  2. Приложение выходит на передний план . Как и когда обновлять данные, когда приложение выходит на передний план. Это необходимо, потому что, если уведомление доставляется, когда приложение находится в фоновом режиме, приложение не запускается, пока пользователь не взаимодействует с уведомлением. Как только пользователь взаимодействует с уведомлением (в том числе просто отклоняет его), я обновляю, обновляю раздел и дату, но если пользователь игнорирует уведомление и загружает приложение, данные устаревают.

  3. Время прошло . Как и когда обновлять данные, пока пользователь находится в приложении, прошло время, и данные должны быть обновлены. Это действительно необходимо только для обхода проблемы № 2.

  4. [Добавлено] Невозможно запустить x-callback-url из уведомления . [Я добавил это со времени своего первоначального поста, потому что это может повлиять на то, как / когда / где делать вещи в моем приложении.] Весь смысл напоминаний в моем приложении заключается в том, что пользователь может запускать другое приложение из уведомлений моих приложений, используя x-callback-url. Я заметил, что если мое приложение было закрыто, уведомления по-прежнему доставляются, но когда пользователь выполняет действие с уведомлением, он просто открывает мое приложение в главном представлении таблицы, но фактически не выполняет действия по запуску другого приложения. что это должно. Я буквально не знаю, почему, и, конечно, я не могу отлаживать приложение, пока оно закрыто ;-) Поэтому я тоже ищу помощь по этому вопросу.

Если это имеет значение, я также в свою очередь ожидаю добавить функциональность x-callback-url в свое собственное приложение. Мне бы хотелось, чтобы другие приложения могли создавать, обновлять или удалять записи напоминаний. Код, связанный с моей обработкой уведомлений, приведен ниже.

  1. Общая передовая практика . Я следовал инструкциям Apple и многих других, смотрел на все виды кода и чувствую, что делаю то, что нужно. Я "привязываю" данные ядра к контроллеру табличного представления и извлекаю данные в viewDidLoad. Тем не менее, это делает контроллер действительно толстым. Возможно, я заинтересован в том, чтобы использовать этот метод, чтобы разбить весь основной код данных на «Контроллер модели». https://medium.com/@maddy.lucky4u/swift-4-core-data-part-3-creating-a-singleton-core-data-refactoring-insert-update-delete-9811af2fcf75 Кажется, это было бы здорово! Контроллер вида намного чище / стройнее, и я думаю, что могу даже «спрятать» все необязательное безобразие в нем. И если вы все в конечном итоге скажете мне, что для решения моих проблем мне нужно переместить выборку и обновление в другое место, например в AppDelegate, это тоже будет проще.

ФОН НА ТО, ЧТО Я ПРОБОВАЛ

Я изучил и попробовал много разных вещей для решения проблем.

  1. Обновить в viewDidLoad после выборки . Я попытался вызвать функцию refreshReminders сразу после выборки в контроллере табличного представления во время viewDidLoad. Кажется, это работает нормально, пока я не сохраняю данные ядра в этой функции. Простая установка полей в записи / записях запускает контроллер, и строки перемещаются и обновляются, как они должны. Хотя кажется, что это слишком поздно в последовательности загрузки. Действительно ли мы хотим, чтобы табличное представление перемещало записи прямо при первом запуске приложения? Это звучит как плохая практика.

Теперь, если я попытаюсь сохранить изменения в основных данных после обновления, это то, где это становится действительно странным. Таблица явно обновляется на основе изменений в записях в текущем контексте, но когда контекст сохраняется, он запускает изменения снова! Поскольку изменения уже были внесены, приложение падает при попытке переместить строку из старого местоположения в новое после того, как оно уже было перемещено. Смотрите код ниже.

  1. Обновить в представленииWillAppear . Я попытался вызвать refreshReminders из viewWillAppear, так как я думаю, что это сработает как при начальной загрузке, так и после того, как приложение выйдет на передний план. Он работает так же, как и в viewDidLoad, в том случае, если не сохранять контекст, он перемещает записи правильно. Если я сохраняю контекст, я получаю ту же ошибку, что и в viewDidLoad.

Однако на самом деле это не срабатывает, когда приложение возвращается на передний план, что меня озадачивает.

У этого также есть отрицательный побочный эффект срабатывания, когда мы возвращаемся из экрана подробного просмотра / редактирования. Нам это не нужно, поскольку segue уже обрабатывает создание новой записи или обновление существующей и сохранение контекста.

  1. Обновить до пробуждения Fetch . Я использовал расширение основного объекта данных (напоминания), чтобы установить раздел и дату следующего оповещения. Тем не менее, это, похоже, ничего не делает. Т.е. таблица все еще загружается с напоминаниями в неправильном разделе и / или порядке сортировки.

Обновление полей здесь также не помечает данные как «грязные» (измененные), поэтому контроллер табличного представления не перемещает их, и сохранение контекста, которое запускается только в том случае, если грязные данные не запускаются конечно. Я даже пытался сохранить контекст после извлечения, не проверяя, изменился ли он, но это тоже ничего не делает. то есть основные данные действительно не думают, что что-то изменилось, даже если оно изменилось.

  1. Обновление по таймеру . Я НЕ пытался обновить напоминания на основе таймера еще. Я полагаю, что если я не могу даже заставить работать обновление во время начальной загрузки и вернуться на передний план, то, по сути, «случайный» вызов обновления для таймера, скорее всего, вызовет сбой. Интересно, что я временно включил функцию pull-to-refresh, и, кажется, я могу сделать это в любое время, и она отлично работает.

ЗДЕСЬ КОД

  1. Обновление после выборки и 2. Обновление в представленииWillAppear

Это случай .move в функции контроллера моего tableViewController

case .move:
            if let oldPath = indexPath, let newPath = newIndexPath {
                os_log("RemindersViewController: Move was triggered, now updating row in table. Old path was %{public}@ and new path is %{public}@", log: OSLog.default, type: .info, oldPath as CVarArg, newPath as CVarArg)
RemindersCell, withReminder: anObject as! Reminders)
                configureCell(tableView.cellForRow(at: oldPath) as! RemindersCell, withReminder: anObject as! Reminders)
                os_log("RemindersViewController: updated moved cell.", log: OSLog.default, type: .info)


                // Don't actually try to move it if the old and new path are the same
                if (newPath != oldPath) {
                    os_log("RemindersViewController: Moving row in table.", log: OSLog.default, type: .info)
                    tableView.moveRow(at: oldPath, to: newPath)
                    os_log("RemindersViewController: row moved.", log: OSLog.default, type: .info)
                }
            }

Это упрощенная версия моей функции configureCell.

    func configureCell(_ cell: RemindersCell, withReminder reminder: Reminders) {
        cell.labelTitleField!.text = reminder.title ?? "New Reminder"
        cell.labelAlertField!.text = reminder.nextAlert!.description
    }

Это то, что в консоли связано сэтот код:

2019-03-23 12:31:09.307801-0500 Scheduler[5711:2218287] RemindersViewContoller in viewDidLoad: Fetched records successfully.
Refreshing reminders!
2019-03-23 12:31:09.311755-0500 Scheduler[5711:2218287] Reminder 'Test Non-recurring Reminder' section updated to Inactive.
2019-03-23 12:31:09.313254-0500 Scheduler[5711:2218287] RemindersViewController: Move was triggered, now updating row in table. Old path was <NSIndexPath: 0x28078e480> {length = 2, path = 0 - 0} and new path is <NSIndexPath: 0x28078f140> {length = 2, path = 1 - 2}
Scheduler was compiled with optimization - stepping may behave oddly; variables may not be available.
(lldb) 

Таким образом, похоже, что сама ОС переместила напоминание, где она находится, каким-то образом без использования функции моего контроллера.Это должно произойти, когда обновляются отдельные поля напоминания, но затем, когда я сохраняю контекст в основных данных, он снова вызывает функцию моего контроллера, чтобы выполнить уже выполненное перемещение.

[Добавлено] Невозможно запустить x-callback-url из уведомления
    // Handle notifications when our app is in the background
    // Note that this isn't triggered when the notification is delivered, but rather when the user interacts with the notification
    //
    // TO-DO: THIS DOESN'T RUN THE SHORTCUT IF THE APP WAS CLOSED WHEN THE NOTIFICATION WAS RESPONDED TO!!!
    //
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: () -> Void) {

        // Get the user info from the notification
        let userInfo = response.notification.request.content.userInfo

        // Get the specific record id that triggered this notification
        let itemID = userInfo["ITEM_ID"] as? String
        let itemTitle = userInfo["ITEM_TITLE"] as? String
        let itemShortcut = userInfo["ITEM_SHORTCUT"] as? String
        let itemURL = userInfo["ITEM_URL"] as? String

        // Handle the notification action
        print("RemindersViewController: Received notification. actionIdentifier:", response.actionIdentifier)
        switch response.actionIdentifier {

        // If user selected the Run Shortcut option or simply tapped the notification, run the associated shortcut
        case "RUN_SHORTCUT", "com.apple.UNNotificationDefaultActionIdentifier":
            os_log("RemindersViewController: Notification Action Received: RUN_SHORTCUT: %{public}@ for shortcut %{public}@", log: .default, type: .info, String(describing: itemTitle!), String(describing: itemShortcut!))
            if (itemShortcut != nil && itemShortcut != "") {
                if (itemURL != nil) {
                    print("RemindersViewController: Shortcut URL=", itemURL!)
                    let launchURL = URL(string: itemURL!)
                    if UIApplication.shared.canOpenURL(launchURL!) {
                        UIApplication.shared.open(launchURL!, options: [:], completionHandler: { (success) in
                            print("RemindersViewController: Notification Action: Run shortcut: Open url : \(success)")
                        })
                    } else {
                        let alert = UIAlertController(title: "You don't have the Shortcuts app installed", message: "Please download from the Apple App Store", preferredStyle: .alert)
                        let action = UIAlertAction(title: "Ok", style: .default, handler: nil)
                        alert.addAction(action)
                        self.present(alert, animated: true, completion: nil)
                        print("RemindersViewController: Notification Action: User doesn't have the Shortcuts app.")
                    }
                } else {
                    let alert = UIAlertController(title: "You don't have a shortcut name filled in", message: "Please fill in a shortcut name on your reminder", preferredStyle: .alert)
                    let action = UIAlertAction(title: "Ok", style: .default, handler: nil)
                    alert.addAction(action)
                    present(alert, animated: true, completion: nil)
                    print("RemindersViewController: Notification Action: No shortcut name filled in!")
                }
            }
            break

        default:
            os_log("RemindersViewController: Default action selected, which is: %{public}@. Doing nothing.", log: .default, type: .info, response.actionIdentifier)
            break
        }

        // Regardless of what the response was, update the reminder with section and nextAlert date
        // REMOVE THIS once we auto-refresh when the app enters the foreground again as that will take care of it
        if (itemID != nil) {
            print("Refreshing reminder from the notification.")
            refreshReminder(stringURI: itemID!)
        }

        // Call the completion handler to close out the notification
        completionHandler()
    }

ОЖИДАЕМЫЕ И РЕАЛЬНЫЕ РЕЗУЛЬТАТЫ

Я думаю, что я рассмотрел это выше, но в заключение я хочу узнать лучшие практики о том, как и когда обновлять зависящие от времени данные при начальной загрузке, возвращаться на передний план и по прошествии времени.[Добавлено] Я также ожидаю, что действия по уведомлению, когда мое приложение закрыто, будут по-прежнему выполнять эту функцию (вызывая другое приложение через x-callback-url), когда пользователь запросит это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...