Realm <Results>теряет данные вне функции - PullRequest
0 голосов
/ 14 марта 2019

Я все еще новичок в кодировании, и у меня возникают некоторые проблемы с Realm Cloud, которые, как бы я ни старался, мне не удается исправить. Я пытаюсь создать образец трекера заказов Click & Collect, который при изменении свойства объекта orderState между 1-4 (числа представляют разные этапы) изменяет интерфейс на соответствующий экран. Я подписался на наблюдение Царства объекта Results<Order>, в функции, где наблюдение и уведомления происходят currentOrder содержит правильный объект Order. Однако у меня есть переключатель для changes из наблюдения, который вызывает функцию для обновления до правильного интерфейса. Внутри этой вызванной функции currentOrder внезапно не содержит данных, currentOrder был определен в глобальной области видимости, поэтому я не могу понять, почему это происходит. Я фильтрую Results<Order> только для запроса соответствующего идентификатора (наблюдения, кажется, не работают для меня вообще при сопоставлении по первичному ключу и в обход Results).

Я собираюсь добавить сюда весь VC, единственное важное замечание - это то, что свойство currentOrderID передается от предыдущего VC, где объект был записан в Realm. Если вы прокрутите вниз до func prepareRealm & func changeUIBasedOnStatus, то здесь кроется проблема, и я также включил консольное сообщение в конце inc. печать результатов выписки.

//
//  TrackerViewController.swift
//  HG Demo
//
//  Created by Adam Woodcock on 12/03/2019.
//  Copyright © 2019 Adam Woodcock. All rights reserved.
//

import UIKit
import RealmSwift
import Lottie
import MapKit
import CoreLocation

class TrackerViewController: UIViewController {
    //Lottie Views
    @IBOutlet weak var orderPlacedAnimation: LOTAnimationView!
    @IBOutlet weak var orderConfirmedAnimation: LOTAnimationView!
    @IBOutlet weak var orderPickedAnimation: LOTAnimationView!
    @IBOutlet weak var orderCompleteAnimation: LOTAnimationView!

    //Outlets
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var headingLabel: UILabel!
    @IBOutlet weak var bodyLabel: UILabel!
    @IBOutlet weak var progressImage: UIImageView!


    let config = SyncUser.current?.configuration()
    var realm : Realm!

    var currentOrder : Results<Order>!
    var currentOrderID : String!
    var subscription : SyncSubscription<Order>!
    var subscriptionToken : NotificationToken?
    var notificationToken : NotificationToken?

    override func viewDidLoad() {
        super.viewDidLoad()
        realm = try! Realm(configuration: config!)
        currentOrder = realm.objects(Order.self).filter("orderID = %@", currentOrderID!)
        prepareRealm()
        startOrderPlacedAnimation()

    }

    //Lottie functions
    func startOrderPlacedAnimation() {
        orderPlacedAnimation.setAnimation(named: "orderPlaced")
        orderPlacedAnimation.play()
        orderPlacedAnimation.loopAnimation = true
        orderConfirmedAnimation.isHidden = true
        orderCompleteAnimation.isHidden = true
        headingLabel.text = "Thank you! Your order has been placed!"
        bodyLabel.text = "Your order has been successfully placed, we'll notify you once this has been accepted!"
        progressImage.image = UIImage(named: "singleCheck")
    }

    func startOrderConfirmedAnimation() {
        orderConfirmedAnimation.isHidden = false
        orderConfirmedAnimation.setAnimation(named: "undedited")
        orderConfirmedAnimation.play()
        orderConfirmedAnimation.loopAnimation = true
        orderPlacedAnimation.isHidden = true
        orderCompleteAnimation.isHidden = true
        headingLabel.text = "It's Official! Your order is confirmed!"
        bodyLabel.text = "A team member has confirmed your order, we'll start packing soon!"
        progressImage.image = UIImage(named: "doubleCheck")
    }

    func startOrderPickedAnimation() {
        orderPickedAnimation.isHidden = false
        orderPickedAnimation.setAnimation(named: "orderPicked")
        orderPickedAnimation.play()
        orderPickedAnimation.loopAnimation = true
        orderPlacedAnimation.isHidden = true
        orderConfirmedAnimation.isHidden = true
        orderCompleteAnimation.isHidden = true
        headingLabel.text = "Woosh! Your order is being packed!"
        bodyLabel.text = "A team member with extremely steady hands is currently packing your order!"
        progressImage.image = UIImage(named: "tripleCheck")

    }

    func startOrderCompleteAnimation() {
        orderCompleteAnimation.isHidden = false
        orderCompleteAnimation.setAnimation(named: "orderComplete")
        orderCompleteAnimation.play()
        orderCompleteAnimation.loopAnimation = true
        orderPlacedAnimation.isHidden = true
        orderConfirmedAnimation.isHidden = true
        orderPickedAnimation.isHidden = true
        headingLabel.text = "Woohoo! Your order is ready to collect!"
        bodyLabel.text = "We're as excited as you, so what're you waiting for? Come and grab it!"
        progressImage.image = UIImage(named: "quadrupleCheck")
    }

    func startOrderHasBeenCollectedAnimation() {

    }

    func startErrorWithOrderAnimation() {

    }

    //Realm functions

    //Assigning the current order to the Order object variable
    func prepareRealm() {
        subscription = currentOrder.subscribe(named: "current-order", limit: nil)
        subscriptionToken = subscription.observe(\.state, options: .initial, { (state) in })
        notificationToken = currentOrder.observe({ (changes) in
            switch changes {
            case .initial:
                self.changeUIBasedOnStatus(sender: "Initial")
            case .update :
                self.changeUIBasedOnStatus(sender: "Update")
            case .error(let error):
                fatalError(error.localizedDescription)
            }
        })
        print("Realm prepared, this is the object: \(currentOrder!)")
        titleLabel.text = "\(String(currentOrder.first!.firstName))'s Order #\(currentOrder.first!.orderID!)"
    }

    func changeUIBasedOnStatus(sender: String) {
        print("The switch realm object contains: \(currentOrder!), sender: \(sender)")
        switch currentOrder.first!.orderStatus {
        case 1:
            startOrderPlacedAnimation()
        case 2:
            startOrderConfirmedAnimation()
        case 3:
            startOrderPickedAnimation()
        case 4:
            startOrderCompleteAnimation()
        case 5:
            startOrderHasBeenCollectedAnimation()
        default:
            startErrorWithOrderAnimation()
        }
    }

    //IBActions
    @IBAction func callUsTapped(_ sender: Any) {
        guard let number = URL(string: "tel://+441522684865") else { return }
        UIApplication.shared.open(number, options: [:], completionHandler: nil)
    }

    @IBAction func openingHoursTapped(_ sender: Any) {
    }

    @IBAction func directionsTapped(_ sender: Any) {
        //Creating an action sheet to ask the user whether they'd like to use Apple Maps or Google Maps
        let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
        //Adding the action and functionality to load Apple maps
        alert.addAction(UIAlertAction(title: "Apple Maps", style: .default, handler: { (action) in
            //Creating a placemark object to pass into the map item
            let placemark = MKPlacemark(coordinate: CLLocationCoordinate2DMake(53.203498, -0.611785))
            //Initialising a new map item object with the pre-made placemark object
            let mapItem = MKMapItem(placemark: placemark)
            mapItem.phoneNumber = "+44 (0) 1522 684865"
            //Setting the launch options to default to driving directions
            let launchOptions = [MKLaunchOptionsDirectionsModeKey:MKLaunchOptionsDirectionsModeDriving]
            //Telling the map item object to open that specific location in maps
            mapItem.openInMaps(launchOptions: launchOptions)
        }))
        alert.addAction(UIAlertAction(title: "Google Maps", style: .default, handler: { (action) in
            //Add Google maps functionality
        }))
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in
            alert.dismiss(animated: true, completion: nil)
        }))
        present(alert, animated: true, completion: nil)
    }

}

Консольное сообщение:

2019-03-14 17:00:52.132718+0000 HG Demo[51949:3038807] Sync: Connection[1]: Connected to endpoint '3.121.59.66:443' (from '192.168.0.21:64953')
Realm prepared, this is the object: Results<Order> <0x7fdce8c2d370> (
    [0] Order {
        firstName = Adam;
        lastName = Woodcock;
        orderID = 4431295;
        timestamp = 2019-03-14 17:00:54 +0000;
        orderStatus = 1;
        isFulfilled = 0;
    }
)
The switch realm object contains: Results<Order> <0x7fdce8c2d370> (

), sender: Initial
(lldb)

Неустранимая ошибка в операторе switch в changeUIBased..., в частности в switch currentOrder.first!.orderStatus возникает ошибка «Неожиданно найден ноль ...».

Я знаю, что это немного затянуто, поэтому спасибо заранее за любую помощь.

[EDIT]

Для пояснения я удалил весь код, связанный с уведомлениями Realm, из функции prepareRealm, я присваиваю currentOrder[0] переменной с именем thisOrder, чтобы сделать ее типа Object, а не введите Results. Затем я печатаю значение thisOrder, в котором порядок правильно печатает значения. Увы, я тогда печатаю thisOrder внутри таймера, и теперь он печатается как [неверный объект]. Таймер является символическим в том смысле, что всякий раз, когда значения currentOrder или thisOrder передаются вне функции prepareRealm или для замыкания, объект становится недействительным. Я делал это несколько раз в разных приложениях и даже в этом приложении на отдельном VC, и он работает на 100% гладко, поэтому я действительно не могу понять, почему это происходит.

func prepareRealm() {
    realm = try! Realm(configuration: config!)
    currentOrder = realm.objects(Order.self).filter("orderID = %@", currentOrderID)
    thisOrder = currentOrder[0]
    print("This is thisOrder: \(thisOrder!)")

    let timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { (timer) in
        print(self.thisOrder)
    }

}

[РЕДАКТИРОВАТЬ 2] Я обновил Realm до последней версии, и все стало работать! Я думал, что это было причиной проблемы, однако с тех пор я продолжал создавать различные элементы, тестировать и т.д. отправить ошибку с ними.

Ответы [ 2 ]

0 голосов
/ 19 марта 2019

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

  1. Создание Order объекта локально
  2. Запись транзакции загружена на сервер
  3. Просмотр запросов контроллера для объектаи находит его.
  4. Контроллер представления создает подписку локально.
  5. Просмотр сообщения журнала контроллера, указывающего, что объект существует.
  6. Сервер обрабатывает транзакцию записи, которая создала объект.Вновь созданный объект не соответствует ни одной из подписок, о которых знает сервер, поэтому он сообщает клиенту об удалении вновь добавленного объекта.
  7. Клиент делает недействительным наблюдаемый объект.
  8. Сервер обрабатываетновая подписка и сообщает клиенту о восстановлении объекта
  9. Клиент восстанавливает объект, но это не восстанавливает недействительную ссылку на него или на наблюдателя.

Если это происходит внемного другой порядок (например, сервер обрабатывает создание объекта и подписку одновременно), все будет работать.

Есть несколько вариантов, чтобы это исправить:

  1. Простособлюдайте Results<Order> и обрабатывайте случай, когда .first временно равен nil
  2. Создайте подписку для объекта перед созданием объекта, чтобы он не был временно удален из клиента.
  3. В контроллере вида просмотрите подписку и подождите, пока она достигнет состояния .complete, прежде чем настраивать объектНаблюдатель ЭСТ.
0 голосов
/ 14 марта 2019

Order должен иметь все свойства, отмеченные модификатором dynamic, чтобы Realm мог переопределять метод получения / установки.

Таким образом, ваш ордер будет выглядеть примерно так:

class Order: Object {
    @objc dynamic var firstName = ""
    @objc dynamic var lastName = ""
    ....
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...