I'm trying to merges two published object from query of two different collections to make a single published array using combineLatest
.
import Combine
import Firebase
class FirestoreViewModel: ObservableObject {
let userId: String
@Published var aList: [DocumentA]? = nil
@Published var bList: [DocumentB]? = nil
init(userId: String, listenForChanges: Bool = false) {
// Get the user
if (listenForChanges) {
self.loadA()
self.loadB()
}
}
var cList: AnyPublisher<[DocumentC]?, Never> {
return Publishers.CombineLatest(
$aList.map { (aList) -> [DocumentC]? in
guard let aList = aList else { return nil }
return aList.map {a in
DocumentC(from: a)
}
},
$bList.map { (bList) -> [DocumentC]? in
guard let bList = bList else { return nil }
return bList.map { n in
DocumentC(from: b)
}
})
.map { (aList, bList) -> [DocumentC]? in
if (aList == nil && bList == nil) { return nil }
var cList: [DocumentC] = []
if let aList = aList {
cList.append(contentsOf: aList)
}
if let bList = bList {
cList.append(contentsOf: bList)
}
return cList
}
.eraseToAnyPublisher()
}
private func loadA() {
// Start listening
Firestore.firestore().collection("colA").addSnapshotListener { (snapshot, error) in
if let error = error {
print("DEBUG: Unable to get user data: \(error.localizedDescription)")
return
}
// Invalid data return
guard let snapshot = snapshot else {
print("DEBUG: null data returned")
self.aList = nil
return
}
// Update the info
var aList: [DocumentA] = []
snapshot.documents.forEach { document in
let aData = document.data()
guard !aData.isEmpty else {
return
}
aList.append(DocumentA(from: aData)
}
self.aList = aList
}
}
private func loadB() {
// Start listening
Firestore.firestore().collection("colB").addSnapshotListener { (snapshot, error) in
if let error = error {
print("DEBUG: Unable to get user data: \(error.localizedDescription)")
return
}
// Invalid data return
guard let snapshot = snapshot else {
print("DEBUG: null data returned")
self.bList = nil
return
}
// Update the info
var bList: [DocumentB] = []
snapshot.documents.forEach { document in
let bData = document.data()
guard !bData.isEmpty else {
return
}
bList.append(DocumentB(from: bData))
}
self.userUnits = userUnits
}
}
}
When I debug, I can see that aList
and bList
are published once, however, the cList
keeps been published eventually eating all the memory...
The cList
is consumed via a onReceive statement in the view, which output each item.
Would anybody be able to tell me why the combineLatest
keeps on publishing cList
, though there are no fresh aList
or bList
published ?
Many thanks in advance.
In all likelihood, you have a situation where you update the view with values just received from cList
, which causes the body
to be recomputed, which causes another onReceive(vm.cList) {...}
, which causes a new publisher to be returned by the computed property cList
, which emits the values again and repeats the cycle.
Here's an simplified example of what I mean:
class ViewModel: ObservableObject {
@Published var aList: [Int] = [1,2]
var cList: AnyPublisher<Int, Never> {
$aList.map { $0 + $0 }.eraseToAnyPublisher()
}
}
struct ContentView: View {
@StateObject var vm = ViewModel()
@State var list: [Int] = []
var body: some View {
VStack {
ForEach(list, id: \.self) { v in
Text("\(v)")
}
}
.onReceive(vm.cList) { self.list = $0 }
}
}
To avoid that, cList
shouldn't be a computed property. It could just be a lazy-ly assigned constant:
lazy var cList: AnyPublisher<Int, Never> =
$alist.map { $0 + $0 }
.eraseToAnyPublisher()