9

I know in general a publisher is more powerful than a closure, however I want to ask and discuss a specific example:

func getNotificationSettingsPublisher() -> AnyPublisher<UNNotificationSettings, Never> {
   let notificationSettingsFuture = Future<UNNotificationSettings, Never> { (promise) in
      UNUserNotificationCenter.current().getNotificationSettings { (settings) in
         promise(.success(settings))
      }
   }
   return notificationSettingsFuture.eraseToAnyPublisher()
}

I think this is a valid example of a Future publisher and it could be used here instead of using a completion handler. Let's do something with it:

func test() {
    getNotificationSettingsPublisher().sink { (notificationSettings) in
       // Do something here        
    }
}

This works, however it will tell me that the result of sink (AnyCancellable) is unused. So whenever I try to get a value, I need to either store the cancellable or assign it until I get a value.

Is there something like sinkOnce or an auto destroy of cancellables? Sometimes I don't need tasks to the cancelled. I could however do this:

func test() {
   self.cancellable = getNotificationSettingsPublisher().sink { [weak self] (notificationSettings) in
      self?.cancellable?.cancel()
      self?.cancellable = nil
   }
}

So once I receive a value, I cancel the subscription. (I could do the same in the completion closure of sink I guess).

What's the correct way of doing so? Because if I use a closure, it will be called as many times as the function is called, and if it is called only once, then I don't need to cancel anything.

Would you say normal completion handlers could be replaced by Combine and if so, how would you handle receiving one value and then cancelling?

Last but not least, the completion is called, do I still need to cancel the subscription? I at least need to update the cancellable and set it to nil right? I assume storing subscriptions in a set is for long running subscriptions, but what about single value subscriptions?

Thanks

Janosch Hübner
  • 1,584
  • 25
  • 44
  • "the completion is called, do I still need to cancel the subscription?" Isn't that what you _want_? Your question is asking "how can I cancel the subscription after the first value is received?" right? – Sweeper May 27 '20 at 07:46
  • Yes I do want to finish the subscription as soon as I get a value. If I need to always actively cancel it, then this adds lots of code to each part of the code where subscriptions are used – Janosch Hübner May 27 '20 at 08:44

1 Answers1

19

Instead of using the .sink operator, you can use the Sink subscriber directly. That way you don't receive an AnyCancellable that you need to save. When the publisher completes the subscription, Combine cleans everything up.

func test() {
    getNotificationSettingsPublisher()
        .subscribe(Subscribers.Sink(
            receiveCompletion: { _ in },
            receiveValue: ({
                print("value: \($0)")
            })
        ))
}
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Doesn't this need a `prefix(1)` or something? The OP seems to want to observe only one value. – Sweeper May 29 '20 at 16:13
  • In the question, `getNotificationSettingsPublisher` is implemented using `Future`. A `Combine.Future` can only ever publish one value before finishing. No `prefix` necessary. – rob mayoff May 29 '20 at 16:44