领域 <Results> 在函数外丢失数据

Realm <Results> Loses Data Outside Function

我对编码还是个新手,我在使用 Realm Cloud 时遇到了一些问题,无论我如何努力,似乎都无法解决。我正在尝试创建一个 'Click & Collect' 订单跟踪器的示例,当 orderState 对象 属性 在 1-4 之间更改(数字代表不同的阶段)时,它会更改 UI 到相应的屏幕。我订阅了 Results<Order> 对象的 Realm 观察,在观察和通知发生的函数中 currentOrder 包含正确的 Order 对象。但是我有一个来自观察的 changes 的开关,它调用一个函数来更新到正确的 UI。在这个被调用的函数 currentOrder 中突然不包含任何数据,currentOrder 是在全局范围内定义的,所以我不明白为什么会这样。我过滤 Results<Order> 以仅查询匹配的 ID(当通过主键匹配并绕过 Results 时,观察结果似乎对我根本不起作用)。

我要在这里添加整个 VC,唯一重要的注意是 currentOrderID 属性 是从前面的 VC 传递过来的该对象已写入 Realm。如果你向下滚动直到 func prepareRealm & func changeUIBasedOnStatus 这就是问题所在,我还在结尾处包含了控制台消息。打印语句结果。

//
//  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 是它抛出 'Unexpectedly found nil...' 错误的地方。

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

[编辑]

为了清楚起见,我从 prepareRealm 函数中删除了所有与 Realm 通知有关的代码,我将 currentOrder[0] 分配给一个名为 thisOrder 的变量来实现Object 类型而非 Results 类型。然后我打印订单正确打印值的 thisOrder 的值。 las,然后我在计时器闭包中打印 thisOrder,它现在打印为 [invalid object]。定时器在某种意义上是象征性的,只要值 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 的问题所以也会向他们提交错误。

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

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

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

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

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

如果这些发生的顺序略有不同(例如,服务器碰巧同时处理对象创建和订阅创建),一切都会正常进行。

有几个选项可以解决这个问题:

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