Description
I have two classes, one of them is implemented using the singleton pattern:
class Singleton: ObservableObject {
static let sharedSingleton = Singleton()
private init() {}
@Published var fetchingData = true
// This is called upon launching the app
func getData() {
fetchingData = true
someNetworkingCalls {
... // fetching data from a db
self.fetchingData = false
}
}
}
Then, I have another class that has a function where a while-loop is used to pause the code execution if the Singleton class is fetching data:
class AnotherClass: ObservableObject {
private var sharedSingleton = Singleton.sharedSingleton
func getAnotherData() {
while (sharedSingleton.fetchingData){// do nothing here}
// then proceed execute another networking call
}
}
Functions from both classes are being called in MainAppView in the following fashion:
@ObservedObject singleton = Singleton.sharedSingleton
@StateObject anotherClass = AnotherClass()
...
SomeView()
.task {
singleton.getData()
anotherClass.getAnotherData()
}
.environmentObject(anotherClass)
...
Problem
The while-loop never terminates even though I’m sure the fetchingData has been set to false.
I could mitigate this problem by wrapping anotherClass.getAnotherData() in an asyncAfter but I highly suspect that it worked only because by the time getAnotherData() is called, the singleton.getData() has already finished its job — this could be very tricky if users have unstable/slow network connections.
I was wondering why the while-loop cannot catch the change made to the @Published var? And how to properly solve this problem?
>Solution :
A while loop is not a reliable way to wait for data to be loaded. Instead, one solution would be to use Combine to watch for changes to the fetchingData Publisher.
That might look like this:
import Combine
class AnotherClass: ObservableObject {
private var sharedSingleton = Singleton.sharedSingleton
private var cancellable : AnyCancellable?
func getAnotherData() {
cancellable = sharedSingleton.$fetchingData.sink { value in
guard value else { return }
//do your next action here
}
}
}
Realistically, it would be better to watch whatever the data itself is, rather than setting up a separate fetchingData. That might look like this:
class Singleton: ObservableObject {
static let sharedSingleton = Singleton()
private init() {}
@Published var data : Data? = nil
// This is called upon launching the app
func getData() {
someNetworkingCalls { result in
self.data = result
}
}
}
class AnotherClass: ObservableObject {
private var sharedSingleton = Singleton.sharedSingleton
private var cancellable : AnyCancellable?
func getAnotherData() {
cancellable = sharedSingleton.$data.sink { data in
guard let data = data else { return }
//do your next action here that depends on `data`
}
}
}