Наблюдайте ответ JSON, если он был изменен с уже сохраненного в базе данных (область) - PullRequest
0 голосов
/ 09 октября 2019

Создание приложения, которое использует Alamofire и Realm для сетевых вызовов и хранения данных соответственно с помощью RxSwift. Все работает нормально, но необходимость дня в том, чтобы постоянно обновлять представления о сетевых вызовах. На данный момент поведение приложения похоже на то, что я копирую ответ JSON в БД, а затем обновляю представления из БД. Но чтобы всегда получать последние ответы, приложение должно вызывать сетевой API на каждом viewWillAppear. Но я не хочу получать все данные БД и искать, если что-то изменилось из нового ответа, а затем отображать его. Поэтому есть ли в Swift или Alamofire или Realm какая-либо вещь, которую я могу наблюдать, если данные отличаются от ранее загруженных в базу данных, и тогда только приложение обновит свое представление.

                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 для этого?

Ссылка на GIF

Хорошо, я делаю это так, есть viewController, в котором у меня есть контейнерное представление, которое имеетКоллекция смотреть как ребенок.

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.

вот код ViewModel, из которого я получаю свои данные и создаю наблюдаемое для CollectionView.

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)

    }
}

1 Ответ

0 голосов
/ 09 октября 2019

Вопрос немного неясен, так что это может быть совершенно неверно. Позвольте мне добавить несколько фрагментов информации, которые могут вписаться в ответ.

Объекты результатов области - это обновляемые объекты. Например, если вы загружаете некоторые объекты Dog

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

, а затем, если вы изменяете имя собаки где-то еще в вашем коде, класс dogResults будет содержать обновленное имя этой собаки.

Таким образом, вам не нужно постоянно обновлять этот объект результатов, поскольку это делается автоматически.

Если вы хотите получать уведомления об этих изменениях, вы добавляете наблюдателя в класс dogResults var.

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

Когда вы впервые добавляете наблюдателя, начальный блок вызывается один раз и только один раз. Это не называется в любое время после этого. Здесь вы можете сказать, обновите свой tableView, чтобы представить исходные данные.

Когда данные изменяются, вызывается блок .update. Вы можете просто перезагрузить tableView снова или внести детальные изменения в tableView на основе измененных данных.

Ваш вопрос гласит:

Но всегда получать последнее приложение для ответанеобходимо вызывать сетевой API для каждого viewWillAppear.

, что не является необходимым, поскольку класс var dogResults всегда содержит обновленную информацию.

и по тем же строкам

каждый раз при сохранении ответа в базу данных при вызове API

не требуется, поскольку единственное время, когда вам потребуется обновить пользовательский интерфейс, находится внутри блока .update.

Наконец, этот кусок кода кажется неуместным

self!.viewModel.getStudentsData(id: classId)

однако в вопросе недостаточно кода, чтобы понять, почему он вызывается в .initial - вы можете рассмотреть возможность использования подходапредставлен выше вместо опроса обновлений.

...