How to solve "Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates." error?

for Hi,


I am retrieving data from a web service in an URLSession and store all the entries to an array


self.warehouseOrders.append(warehouseOrder)


The array is used in another view as data source for a List() item but now I get this error message:


Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.


My question is: where is SwiftUI expecting me to put the operator receive(on:)) to fix this?

I have kinda solved it by changing the code to


DispatchQueue.main.async { 
  self.warehouseOrders.append(warehouseOrder) 
  }


but I am not sure if that is the correct approach here?

See https://forums.developer.apple.com/thread/128068for more details on my code - I have actually posted this already there as a reply but for some reason it still gets moderated for almost 24 hours ...


Max

Post not yet marked as solved Up vote post of bobandsee Down vote post of bobandsee
29k views

Replies

URLSession.shared.dataTaskPublisher(for: urlRequest)
.tryMap {
   // your httpHandlerData
}
.receive(on: RunLoop.main)
.eraseToAnyPublisher()


Just add .receive(on: RunLoop.main) operator before .eraseToAnyPublisher() and That's it.

I am getting the same message when setting up UserDefaults with combine. The code is simple enough
Code Block     @UserDefault("keyTwitterOauthTokenSecret", defaultValue: "")
    var twitterOauthTokenSecret: String {
        willSet{
            objectWillChange.send(self)
        }
    }
    init() {
        notificationSubscription = NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification).sink { _ in
                   self.objectWillChange.send(self)
        }
    }

Not sure I understand the recommended solution above by @gandhi_ms. Can you clarify how I would use this in a this case?
This can help
Code Block
let url = ..... // your url
var warehouseOrders = .... // your array
private var cancelable: AnyCancellable?
private static let sessionProcessingQueue = DispatchQueue(label: "SessionProcessingQueue")
private func loadWarehouseOrders() {
    cancelable = URLSession.shared.dataTaskPublisher(for: url)
      .subscribe(on: Self.sessionProcessingQueue)
      .map({
        print("\n $0.data: \($0.data)")
        return $0.data
        })
      .decode(type: WarehouseOrder.self, decoder: JSONDecoder()) // if you have a WarehouseOrder model
      .receive(on: DispatchQueue.main) // <- here is the problem, you are missing this
      .sink(receiveCompletion: { (suscriberCompletion) in
        switch suscriberCompletion {
        case .finished:
// do something that you want to do when finished
          break
        case .failure(let error):
          print(error.localizedDescription)
        }
      }, receiveValue: { [weak self] (warehouseOrder) in
        self?.warehouseOrders.append(warehouseOrder)
      })
  }
deinit {
    self.cancel()
}
func cancel(){
    cancelable.cancel()
}

I have this "purple warning" on line 31, however my code only works without .receive(on: DispatchQueue.main)
If I set .receive(on: DispatchQueue.main) or .receive(on: RunLoop.main), or simply run on DispatchQueue.main.async {}
the publisher never fires.

Any clues? Is it ok to keep it "purple" ?

Code Block swift
class ImageLoader: ObservableObject {
  private var cancellable: AnyCancellable?
  private var cache: ImageCache
  @Published var image: UIImage?
  private let url: URL
  init(url: URL, cache: ImageCache) {
    self.url = url
    self.cache = cache
  }
  deinit {
    cancellable?.cancel()
  }
  func load() {
    if let image = cache[url] {
      dispatchToMainThreadIfNeeded {
        self.image = image
      }
      return
    }
    cancellable = URLSession.shared.dataTaskPublisher(for: url)
      .map { UIImage(data: $0.data) }
      .handleEvents(receiveOutput: { [weak self] in self?.cache($0) })
      .replaceError(with: nil)
      .sink { value in
        self.image = value // Dispatch on main thread is not firing the publisher..
    }
  }
  func cancel() {
    cancellable?.cancel()
  }
  private func cache(_ image: UIImage?) {
    image.map { cache[url] = $0 }
  }
}


UDP

what sort of work is .assign(to: \.image, on: self) instead of .sink { self.image = $0 }. Seems like only on device, not on Simulator..
The Notification should be posted from main thread, if your post is post from escaping closure or other asynchronous ways, the warning will show up. to avoid this, you can post the noti from main thread as:
Code Block
DispatchQueue.main.async {                   
NotificationCenter.default.post()
}

hope could help.

                
The Notification should be posted from main thread, if your post is post from escaping closure or other asynchronous ways, the warning will show up. to avoid this, you can post the noti from main thread as:
Code Block
DispatchQueue.main.async {                   
NotificationCenter.default.post()
}


hope could help.
Is there a breakpoint I can set to intercept the moment the non-main post occurs? I believe I've properly addressed the cases where it could occur but it's still happening anyway.
Answering my own question: Yes. when the issue occurs, go to the issues navigator, control-click any of the main-thread issues, and choose Create Breakpoint from the context menu. All main-thread issue will now break. (Xcode 9 used to have a "Pause on issues" checkbox but that is gone in Xcode 12.)

I got the error to go away by changing the .onAppear modifier to .task on a SwiftUI view observing a published property.

Below the navigation title modifier of the view.

.navigationTitle("Where you're at")

.task {
                // Fetch data from CloudKit here
                viewModel.fetchData()
            }

When adding @MainActor on whole viewModel class then those messages disappearing but if those messages still are and if that @MainActor no need, then how much important those messages?