Skip to main content

The purple warning notification of annoyance recently came my way!  I received the following warning:

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.

 

What was happening?

The functionality of the code uses URLSession to get a user's name via API and the display is simple piece that initially message of Hello {user}.

The view code consists of:

  • MainView : View
  • Main : ObservableObject
    • func GetData
    • func Headers
    • func ProcessData

The function ProcessData taps into the URLSession via a APIClient that we have added.  Rather than heading to far down the remaining code, I'm to stay focused on the structure MainView and class Main.

struct MainView: View {

  @ObservedObject var main = Main()

  init() {
    main.path = "38/none"
    main.GetData()
  }

  var body: some View {
    GeometryReader { geometry in
      ZStack {
        Image("bgkMain")
          .resizable()
          .aspectRatio(geometry.size, contentMode: .fill)
          .overlay(TintOverlay().opacity(0.9))
          .edgesIgnoringSafeArea(.all)
        VStack(spacing: 0) {
          Text("Hello, \(self.main.user.firstName)")
            .headerText(color: Color.white)
            .padding(.top, 20)
            .padding(.bottom, 90)
        }
      }
    }
  }
}


struct MainView_Previews: PreviewProvider {
    static var previews: some View {
        MainView()
    }
}

The above code uses an observed object class titled Main to display the first name of the user (self.main.user.firstName).

 

class Main : ObservableObject {

  @Published var path: String = ""
  @Published var request = APIRequest(method: .get, path: "")
  @Published var user: User = User.init(id: UUID(), uid: "", firstName: "", lastName: "", nickName: "", name: "", email: "", phone: "", langcode: "", imagePath: "", timezone: "")

  
  func GetData() {

    self.Headers()

    self.ProcessData() { user, status in
        DispatchQueue.main.async {
            if user != nil {
                self.user = user!
            }
        }
    }

  }


  /*
   Set up the request path and headers for the URL Session
   */

  func Headers() {

    self.request.path = self.path

    let authorizationToken = GlobalConfig.GenerateAuthToken(userString: GlobalConfig.Manager1)

    // set the request headers
    self.request.headers = [
      HTTPHeader(
        field: "Accept", value: "application/hal+json"),
      HTTPHeader(
        field: "Authorization", value: authorizationToken),
      HTTPHeader(
        field: "Content-Type", value: "application/hal+json")
    ]
  }

  

  /*
   Process the request and on success return the User
   */
  func ProcessData(completionHandler: @escaping (User?, Int?)-> Void) {

    APIClient().perform(self.request) { (result) in
      switch result {
        case .success(let response):
          // get the status response
          let statusCode = response.statusCode
          if (statusCode == 200 || statusCode == 201) {
            let decoder = JSONDecoder()
            let user = try! decoder.decode(User.self, from: response.body!)
            completionHandler(user, response.statusCode)
          } else {
            print("statusCode error \(statusCode)")
            completionHandler(nil, response.statusCode)
          }

        case .failure:
          print("Error perform network request")
          completionHandler(nil, nil)
      }
    }
  }
}

 

Creating the warning through controlled method

If you run this complete code, there will be no error.  However, to bring on the error you only need to change one area.  That is in the GetData function.  More specifically the area of code where the data is retrieved from the URLSession call.

self.ProcessData() { user, status in
    DispatchQueue.main.async {
        if user != nil {
            self.user = user!
        }
    }
}

Adding a self command after the dispatch queue will throw the error.  Something as innocuous as self.path = "next" will generate the warning.

self.ProcessData() { user, status in
    DispatchQueue.main.async {
        if user != nil {
            self.user = user!
        }
    }
    self.path = "next"
}

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.

The reason is the needs to contain the last task.

Similarly swapping the order of the ProcessData and DispatchQueue again will throw the error

DispatchQueue.main.async {
  self.ProcessData() { user, status in
    if user != nil {
      self.user = user!
    }
  }
}