领域<结果>在功能外丢失数据

时间:2019-03-14 17:04:12

标签: ios swift realm

我还是编码新手,我遇到了Realm Cloud的一些问题,无论我多么努力,似乎都无法解决。我正在尝试创建“点击并收集”订单跟踪器的示例,当orderState对象属性在1-4(数字代表不同阶段)之间更改时,它将UI更改为相应的屏幕。我已经订阅了Results<Order>对象的Realm观察,在该函数中,观察和通知发生的地方currentOrder包含正确的Order对象。但是,从观察到的changes有一个开关,它调用一个函数来更新到正确的UI。在这个称为函数currentOrder的内部突然不包含任何数据,currentOrder是在全局范围内定义的,因此我无法理解为什么会这样。我过滤了Results<Order>只查询匹配的ID(通过主键匹配并绕过Results时,观察结果似乎对我没有任何作用)。

我将在此处添加整个VC,唯一重要的注意事项是currentOrderID属性是从先前将对象写入Realm的VC传递过来的。如果向下滚动直到func prepareRealmfunc 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)

致命错误出现在changeUIBased...的switch语句上,特别是switch currentOrder.first!.orderStatus会引发“意外发现nil ...”错误。

我知道这有点麻烦,所以在此先感谢您的帮助。

[编辑]

为了澄清起见,我从prepareRealm函数中删除了所有与Realm通知有关的代码,我将currentOrder[0]分配给一个名为thisOrder的变量,以使其成为类型Object而非Results类型。然后,我打印thisOrder的值,订单将正确地打印这些值。 las,然后我在计时器闭包内打印thisOrder,现在将其打印为[无效对象]。从意义上说,计时器是象征性的,只要将值currentOrderthisOrder传递到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更新到最新版本,一切开始正常!我以为这是问题的根源,但是自那以后我一直在进行不同的元素构建和测试等工作,突然之间,每次它都开始再次执行此操作,我觉得这可能是Realm的问题,因此与他们一起提交错误。

2 个答案:

答案 0 :(得分:0)

Order必须具有用dynamic修饰符标记的所有属性,Realm才能覆盖getter / setter。

因此您的订单将如下所示:

class Order: Object {
    @objc dynamic var firstName = ""
    @objc dynamic var lastName = ""
    ....
}

答案 1 :(得分:0)

这里的问题是,在后台发生了很多事情,这些事情可能以不一致的顺序发生,因为这涉及到与服务器的对话。可能引起问题的一系列可能的事件是:

  1. 在本地创建Order对象
  2. 写入交易已上传到服务器
  3. 视图控制器查询并找到对象。
  4. View Controller在本地创建订阅。
  5. 视图控制器记录指示对象存在的消息。
  6. 服务器处理创建对象的写事务。新创建的对象与服务器知道的任何订阅都不匹配,因此它告诉客户端删除新添加的对象。
  7. 客户端使观察到的对象无效。
  8. 服务器处理新订阅并告诉客户端还原对象
  9. 客户端还原对象,但不会还原对该对象或观察者的无效引用。

如果这些操作以略有不同的顺序发生(例如,服务器恰巧同时处理对象创建和订阅创建),则一切正常。

有几种方法可以解决此问题:

  1. 只需观察Results<Order>并处理.first暂时为nil的情况
  2. 在创建对象之前为该对象创建订阅,这样就不会将其临时从客户端中删除。
  3. 在视图控制器中,观察订阅并等待其达到.complete状态,然后再设置对象观察器。