如果更改已保存在数据库(领域)中,则观察 JSON 响应
Observe JSON response if changed from already saved in Database(Realm)
在 RxSwift
的帮助下构建一个应用程序,分别使用 Alamofire
和 Realm
进行网络调用和数据存储。一切正常,但今天的需要是防止总是刷新网络调用的视图。现在应用程序的行为就像我将 JSON 响应复制到数据库,然后从数据库更新视图。但要始终获得最新响应,应用程序需要在每个 viewWillAppear
上调用网络 API。但是我不想获取所有数据库数据并搜索新响应是否有变化然后显示它。那么 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 中有什么东西吗?
Link to 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.
这是我从中获取数据并为 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)
}
}
然后我在它的 class 中单独填充 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)
}
以下是 Main 的全部代码 Viewcontroller
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 结果对象是实时更新对象。例如,如果您加载一些狗对象
var dogResults: Results<DogClass>? = nil //define a class var
self.dogResults = realm.objects(Dog.self) //populate the class var
然后,如果您在代码中的其他地方更改狗名,dogResults class var 将包含该狗的更新名称。
因此,您不需要不断刷新该结果对象,因为它会自动完成。
如果您想收到这些更改的通知,请向 dogResults 添加一个观察者 class var.
self.notificationToken = self.dogResults!.observe { (changes: RealmCollectionChange) in
当您第一次添加观察者时,.initial 块将被调用一次且仅调用一次。之后的任何时候都不会调用它。这就是你会说的,刷新你的 tableView 来呈现初始数据。
当数据更改时,将调用 .update 块。您可以选择重新加载 tableView,也可以根据更改后的数据对 tableView 进行细粒度更改。
你的问题是
But to always get latest response app needs to call Network API on
every viewWillAppear.
这不是必需的,因为 class var dogResults 始终包含更新的信息。
并沿着同样的路线
every time save response into database when I call the API
不是必需的,因为您唯一需要更新 UI 的时间来自 .update 块。
最后,这段代码好像不太对劲
self!.viewModel.getStudentsData(id: classId)
但是,问题中没有足够的代码来理解为什么在 .initial 中调用它 - 您可能需要考虑使用上面介绍的方法而不是轮询更新。
在 RxSwift
的帮助下构建一个应用程序,分别使用 Alamofire
和 Realm
进行网络调用和数据存储。一切正常,但今天的需要是防止总是刷新网络调用的视图。现在应用程序的行为就像我将 JSON 响应复制到数据库,然后从数据库更新视图。但要始终获得最新响应,应用程序需要在每个 viewWillAppear
上调用网络 API。但是我不想获取所有数据库数据并搜索新响应是否有变化然后显示它。那么 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 中有什么东西吗?
Link to 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.
这是我从中获取数据并为 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)
}
}
然后我在它的 class 中单独填充 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)
}
以下是 Main 的全部代码 Viewcontroller
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 结果对象是实时更新对象。例如,如果您加载一些狗对象
var dogResults: Results<DogClass>? = nil //define a class var
self.dogResults = realm.objects(Dog.self) //populate the class var
然后,如果您在代码中的其他地方更改狗名,dogResults class var 将包含该狗的更新名称。
因此,您不需要不断刷新该结果对象,因为它会自动完成。
如果您想收到这些更改的通知,请向 dogResults 添加一个观察者 class var.
self.notificationToken = self.dogResults!.observe { (changes: RealmCollectionChange) in
当您第一次添加观察者时,.initial 块将被调用一次且仅调用一次。之后的任何时候都不会调用它。这就是你会说的,刷新你的 tableView 来呈现初始数据。
当数据更改时,将调用 .update 块。您可以选择重新加载 tableView,也可以根据更改后的数据对 tableView 进行细粒度更改。
你的问题是
But to always get latest response app needs to call Network API on every viewWillAppear.
这不是必需的,因为 class var dogResults 始终包含更新的信息。
并沿着同样的路线
every time save response into database when I call the API
不是必需的,因为您唯一需要更新 UI 的时间来自 .update 块。
最后,这段代码好像不太对劲
self!.viewModel.getStudentsData(id: classId)
但是,问题中没有足够的代码来理解为什么在 .initial 中调用它 - 您可能需要考虑使用上面介绍的方法而不是轮询更新。