title: Pausing a Notification publisher in SwiftUI
body: <p>When I call a backend service (login, value check…) I use a Notification publisher on the concerned Views to manage the update asynchronously. 
  I want to unsubscribe to the notifications when the view disappear, or « pause » the publisher. 
  I went first with the simple « assign » option from the WWDC19 Combine and related SwiftUI talks, then I looked at this <a href="https://stackoverflow.com/q/56491881/2999457">great post</a> and the onReceive modifier. However the view keeps updating with the published value even when the view is not visible.</p>
  
  <p>My questions are: </p>
  
  <ol>
  <li>Can I « pause » this publisher when the view is not visible ?</li>
  <li>Should I really be concerned by this, does it affect resources (the backend update could trigger a big refresh on list and images display) or should I just let SwiftUI manage under the hood ?</li>
  </ol>
  
  <p>The sample code:
  <strong>Option 1: onReceive</strong></p>
  
  <pre><code>struct ContentView: View {
  
      @State var info:String = "???"
      let provider = DataProvider() // Local for demo purpose, use another pattern
  
      let publisher = NotificationCenter.default.publisher(for: DataProvider.updated)
      .map { notification in
          return notification.userInfo?["data"] as! String
      }
      .receive(on: RunLoop.main)
  
      var body: some View {
          TabView {
              VStack {
                  Text("Info: \(info)")
                  Button(action: {
                      self.provider.startNotifications()
                  }) {
                      Text("Start notifications")
                  }
              }
             .onReceive(publisher) { (payload) in
                      self.info = payload
              }
              .tabItem {
                  Image(systemName: "1.circle")
                  Text("Notifications")
              }
              VStack {
                  Text("AnotherView")
              }
              .tabItem {
                  Image(systemName: "2.circle")
                  Text("Nothing")
              }
          }
      }
  }
  </code></pre>
  
  <p><strong>Option 2: onAppear / onDisappear</strong></p>
  
  <pre><code>struct ContentView: View {
  
      @State var info:String = "???"
      let provider = DataProvider() // Local for demo purpose, use another pattern
  
      @State var cancel: AnyCancellable? = nil
  
      var body: some View {
          TabView {
              VStack {
                  Text("Info: \(info)")
                  Button(action: {
                      self.provider.startNotifications()
                  }) {
                      Text("Start notifications")
                  }
              }
              .onAppear(perform: subscribeToNotifications)
              .onDisappear(perform: unsubscribeToNotifications)
              .tabItem {
                  Image(systemName: "1.circle")
                  Text("Notifications")
              }
              VStack {
                  Text("AnotherView")
              }
              .tabItem {
                  Image(systemName: "2.circle")
                  Text("Nothing")
              }
          }
      }
  
      private func subscribeToNotifications() {
         // publisher to emit events when the default NotificationCenter broadcasts the notification
          let publisher = NotificationCenter.default.publisher(for: DataProvider.updated)
              .map { notification in
                  return notification.userInfo?["data"] as! String
              }
              .receive(on: RunLoop.main)
  
          // keep reference to Cancellable, and assign String value to property
          cancel = publisher.assign(to: \.info, on: self)
      }
  
      private func unsubscribeToNotifications() {
         guard cancel != nil else {
              return
          }
          cancel?.cancel()
      }
  }
  </code></pre>
  
  <p>For this test, I use a dummy service:</p>
  
  <pre><code>class DataProvider {   
      static let updated = Notification.Name("Updated")
      var payload = "nothing"    
      private var running = true
  
      func fetchSomeData() {
          payload = Date().description
          print("DEBUG new payload : \(payload)")
          let dictionary = ["data":payload] // key 'data' provides payload
          NotificationCenter.default.post(name: DataProvider.updated, object: self, userInfo: dictionary)
      }
  
      func startNotifications() {
          running = true
          runNotification()
      }
  
      private func runNotification() {
          if self.running {
              self.fetchSomeData()
              let soon = DispatchTime.now().advanced(by: DispatchTimeInterval.seconds(3))
              DispatchQueue.main.asyncAfter(deadline: soon) {
                  self.runNotification()
              }
          } else {
              print("DEBUG runNotification will no longer run")
          }
      }
  
      func stopNotifications() {
          running = false
      }   
  }
  </code></pre>