Home > Software engineering >  Why changes made to a @Published variable do not affect the while-loop condition?
Why changes made to a @Published variable do not affect the while-loop condition?

Time:01-28

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?

CodePudding user response:

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`
        }
    }
}
  •  Tags:  
  • Related