如果从已保存在数据库(领域)中更改,则观察 JSON 响应

Posted

技术标签:

【中文标题】如果从已保存在数据库(领域)中更改,则观察 JSON 响应【英文标题】:Observe JSON response if changed from already saved in Database(Realm) 【发布时间】:2019-10-08 23:51:21 【问题描述】:

RxSwift 的帮助下,构建一个使用AlamofireRealm 分别进行网络调用和数据存储的应用程序。一切正常,但当天的需要是防止总是刷新网络调用的视图。现在应用程序的行为就像我将 JSON 响应复制到数据库,然后从数据库更新视图。但是要始终获得最新的响应,应用程序需要在每个 viewWillAppear 上调用网络 API。但是我不想获取所有数据库数据并搜索新响应是否有任何更改然后显示它。那么SwiftAlamofireRealm 中是否有任何东西可以让我观察到数据是否与之前加载到数据库中的数据不同,然后只有应用程序会更新其视图。

                self?.notificationToken = Database.singleton.fetchStudentsForAttendence(byCLassId: classId).observe  [weak self] change in
                switch change 
                case .initial(let initial):
                    TestDebug.debugInfo(fileName: "", message: "INIT: \(initial)")
                    self!.viewModel.getStudentsData(id: classId)
                case .update(_, let deletions, let insertions, let modifications):
                    print(modifications)
                    TestDebug.debugInfo(fileName: "", message: "MODIFY: \(modifications)")
                    TestDebug.debugInfo(fileName: "", message: "MODIFY: \(insertions)")
                case .error(let error):
                    TestDebug.debugInfo(fileName: "", message: "ERROR:\(error)")
                
            

这就是我现在观察数据的方式,但是当我每次调用 API 时,我都会将响应保存到数据库中,并且我使用 case .initial 来监控它,因为数据库总是被刷新并且每次都会调用这个块.我需要一些东西来监控数据库中数据值的变化。 Realm 中有什么东西吗?

Link to GIF

好的,我正在这样做,有一个 viewController,其中我有一个容器视图,其中包含 Collection 视图作为子视图。

private lazy var studentsViewController: AttandenceView = 
    let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
    var viewController = storyboard.instantiateViewController(withIdentifier: "attendenceCV") as! AttandenceView
    self.add(asChildViewController: viewController, to: studentsView)
    return viewController
()  //this way I am adding collectionView's container view.

这是我从中获取数据并为 CollectionView 创建可观察对象的 ViewModel 代码。

class AttendenceVM 

    //MARK: Properties
    let disposeBag = DisposeBag()
    let studentCells = BehaviorRelay<[StudentModel]>(value: [])

    var studentCell : Observable<[StudentModel]> 
        return studentCells.asObservable().debug("CELL")
    

    var notificationToken : NotificationToken? = nil

    deinit 
        notificationToken?.invalidate()
    
    func getStudentsData(id: Int) 
        let studentsData = (Database.singleton.fetchStudentsForAttendence(byCLassId: id))
        self.notificationToken = studentsData.observe[weak self] change in
            TestDebug.debugInfo(fileName: "", message: "Switch:::: change")
            switch change 
            case .initial(let initial):
                TestDebug.debugInfo(fileName: "", message: "INIT: \(initial)")
                self!.studentCells.accept(Array(studentsData))
            case .update(_, let deletions, let insertions, let modifications):
                TestDebug.debugInfo(fileName: "", message: "MODIF::: \(modifications)")
                self!.studentCells.accept(Array(studentsData))
            case .error(let error):
                print(error)
            
        
        //self.studentCells.accept(studentsData)
    

然后我在它的类中分别填充collectionView,通过这样做。

class AttandenceView: UIViewController, UICollectionViewDelegateFlowLayout 

    //MARK: - Outlets
    @IBOutlet weak var studentsView: UICollectionView!

    let studentCells = BehaviorRelay<[StudentModel]>(value: [])
    let scanStudentCells = BehaviorRelay<[ClassStudent]>(value: [])



    private let disposeBag = DisposeBag()

    override func viewDidLoad() 
        super.viewDidLoad()

        let flowLayout = UICollectionViewFlowLayout()
        let size = CGSize(width: 105, height: 135)
        flowLayout.itemSize = size
        studentsView.setCollectionViewLayout(flowLayout, animated: true)
        studentsView.rx.setDelegate(self).disposed(by: disposeBag)
        setupBinding()
    

    func setupBinding() 

        studentsView.register(UINib(nibName: "StudentCVCell", bundle: nil), forCellWithReuseIdentifier: "studentCV")


            //Cell creation
            scanStudentCells.asObservable().debug("Cell Creation").bind(to: studentsView.rx.items(cellIdentifier: "studentCV", cellType: StudentCVCell.self)) 
                (row , element, cell) in
                if (element.attandance == 1 ) 
                    // update view accordingly
                 else if (element.attandance == 0) 
                    // update view accordingly
                 else if (element.attandance == 2) 
                     // update view accordingly  

                
                cell.viewModel2 = element
                .disposed(by: disposeBag)

            //Item Display
            studentsView.rx
                .willDisplayCell
                .subscribe(onNext: ( (cell,indexPath) in
                    cell.alpha = 0
                    let transform = CATransform3DTranslate(CATransform3DIdentity, -250, 0, 0)
                    cell.layer.transform = transform
                    UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5, options: .curveEaseOut, animations: 
                        cell.alpha = 1
                        cell.layer.transform = CATransform3DIdentity
                    , completion: nil)
                )).disposed(by: disposeBag)

            // item selection with model details.
            Observable
                .zip(
                    studentsView
                        .rx
                        .itemSelected,
                    studentsView
                        .rx
                        .modelSelected(StudentModel.self))
                .bind  [weak self] indexPath, model in

                    let cell = self?.studentsView.cellForItem(at: indexPath) as? StudentCVCell
                    if (model.attandance == 0) 
                       // update view accordingly  

                     else if (model.attandance == 1) 
                        // update view accordingly  

                     else if (model.attandance == 2) 
                        // update view accordingly  
                    

                .disposed(by: disposeBag)

         

以下是主视图控制器的完整代码

class AttendanceViewController: MainViewController 
    let viewModel: AttendenceVM = AttendenceVM()
    private let disposeBag = DisposeBag()
    let appDelegate = (UIApplication.shared.delegate as! AppDelegate)
    let notificationCenter = NotificationCenter.default
    var students : Results<StudentModel>? = nil
    var notificationToken: NotificationToken? = nil

    private lazy var studentsViewController: AttandenceView = 
        let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
        var viewController = storyboard.instantiateViewController(withIdentifier: "attendenceCV") as! AttandenceView
        self.add(asChildViewController: viewController, to: studentsView)
        return viewController
    ()
     override func viewDidLoad() 
        super.viewDidLoad()

        if AppFunctions.getAssignedClassId(forKey: "assignedClassId") != 0  // && pref id isAssigned == true
            let id = AppFunctions.getAssignedClassId(forKey: "assignedClassId")
            self.viewModel.getStudentsData(id: id)
        

        bindViewModel()

    
    override func viewWillAppear(_ animated: Bool) 
        super.viewWillAppear(animated)

        classNameLbl.text = "Attandence: Class \(AppFunctions.getAssignedClassName(forKey: "assignedClassName"))"
        students = Database.singleton.fetchStudents(byAttandence: 0, byclassId: AppFunctions.getAssignedClassId(forKey: "assignedClassId"))
        notificationToken = students?.observe [weak self] change in
            self!.studentAbsentLbl.text = "Students Absent (\(String(describing: self!.students!.count)))"
        
        if AppFunctions.getAssignedClassId(forKey: "assignedClassId") != 0  // && pref id isAssigned == true
            let id = AppFunctions.getAssignedClassId(forKey: "assignedClassId")
            getAssignedClassData(classId: id)
        
    
    deinit 
        notificationToken?.invalidate()
    
    func getAssignedClassData(classId: Int) 
        return APIService.singelton
            .getClassById(classId: classId)
            .subscribe( [weak self] _ in
                TestDebug.debugInfo(fileName: "", message: "\(classId)")
//                self?.notificationToken = Database.singleton.fetchStudentsForAttendence(byCLassId: classId).observe  [weak self] change in
//                    switch change 
//                    case .initial(let initial):
//                        TestDebug.debugInfo(fileName: "", message: "INIT: \(initial)")
//                        //self!.viewModel.getStudentsData(id: classId)
//                    case .update(_, let deletions, let insertions, let modifications):
//                        print(modifications)
//                        TestDebug.debugInfo(fileName: "", message: "MODIFY: \(modifications)")
//                        TestDebug.debugInfo(fileName: "", message: "MODIFY: \(insertions)")
//                    case .error(let error):
//                        TestDebug.debugInfo(fileName: "", message: "ERROR:\(error)")
//                    
//                
            )
            .disposed(by: self.disposeBag)
    

    func bindViewModel() 
        viewModel
            .studentCell
            .asObservable()
            .observeOn(MainScheduler.instance)
            .bind(to: studentsViewController.studentCells)
            .disposed(by: disposeBag)

    

【问题讨论】:

使用领域通知。 能带来什么改变?能详细点吗 这是我最喜欢的 Realm 功能。您设置观察者,并且任何时候数据更改视图都会更新。有一些很棒的教程可用。 好的,我会试试的。感谢您的信息 嗨,还需要问一件事吗? 【参考方案1】:

这个问题有点不清楚,所以这可能完全不符合实际。让我添加几条可能适合答案的信息。

领域结果对象是实时更新对象。例如,如果您加载了一些 Dog 对象

var dogResults: Results<DogClass>? = nil //define a class var
self.dogResults = realm.objects(Dog.self) //populate the class var

然后,如果您在代码中的其他位置更改狗名,dogResults 类 var 将包含该狗的更新名称。

因此,您无需不断刷新该结果对象,因为它会自动完成。

如果您想收到有关这些更改的通知,请向 dogResults 类 var 添加一个观察者。

self.notificationToken = self.dogResults!.observe  (changes: RealmCollectionChange) in

当您第一次添加观察者时,.initial 块被调用一次且仅一次。之后的任何时候都不会调用它。这就是您所说的,刷新您的 tableView 以显示初始数据。

当数据发生变化时,会调用 .update 块。您可以选择再次重新加载 tableView,也可以根据更改的数据对 tableView 进行细粒度更改。

您的问题说明

但要始终获得最新响应,应用需要调用 Network API on 每个 viewWillAppear。

这不是必需的,因为类 var dogResults 总是包含更新的信息。

同样的思路

每次调用 API 时都将响应保存到数据库中

没有必要,因为您需要更新 UI 的唯一时间是在 .update 块内。

最后,这段代码好像不合适

self!.viewModel.getStudentsData(id: classId)

但是,问题中没有足够的代码来理解为什么在 .initial 中调用它 - 您可能需要考虑使用上面介绍的方法而不是轮询更新。

【讨论】:

好的,但是我没有从您的精彩回答中得到一件事是,如果我有一个表格视图并且其中有数据,并且我有不同的单元格类型,并且在 api 调用之后随着领域的实时更新,它们都会更新新数据。但我的观点并没有像数据更新一样发生变化。即使在切换标签后,我也可以看到数据正在更新。但是我可以通知我的视图,即使我从其他视图控制器回来,它们也没有被更改,而无需重新加载它们。我该如何处理? 除非您专门调用 .tableView.reloadData 或使用像 tableView.deleteRows(at: deletions. 这样的调用执行细粒度更新,否则 tableViews 不会得到更新。 my view are not shift 是什么意思……我不熟悉shifting。另外,您在此处问什么但我可以在不重新加载视图的情况下通知我的视图?至于更新视图 - 这实际上取决于您的应用程序的设置方式。父视图可以告诉子视图在收到 Realm .update 事件时重新加载它们的 tableView,或者它们可以维护自己的观察者。 我明白你在说什么,如果你听不清楚,我很抱歉,我正在寻求的是只更新我的视图,如重新加载或删除数据库正在更新的东西与之前的数据不同。正如我在代码中显示的那样,我可以在观察者块中做到这一点。但我的主要问题是,当网络调用进行时,我观察到使用上面的代码它只进入Initial 而不是update。那是我只想重新加载表格的地方。如果你明白我的意思,我怎么能做到这一点。 我上传了一个GIF以便更好地理解,在GIF中有两个加载数据的转换,一个来自已经存在的DB,另一个是在我上面描述的那个API调用之后,现在我不希望这种情况像两次转换一样发生,只有当数据库中的数据发生变化时。 @Ahsan 我明白你在做什么。但是,您编辑了我的答案,并且应该针对您的问题进行编辑以使其更加完整。

以上是关于如果从已保存在数据库(领域)中更改,则观察 JSON 响应的主要内容,如果未能解决你的问题,请参考以下文章

将 JSON 保存到文件,如果存在则更改输出

提示用户保存表单

输入未更改时观察者未触发更新事件 - Laravel

KnockoutJS:模板未在可观察数组更改时更新(仅在添加时,在删除时有效)

如何在不更改代码的情况下从已编译的 jar 中检索完整的异常消息?

为啥 ISession 保存不插入?