Combine ownership model: Subscription -> Subscriber -> Publisher

Hello @icanzilb ,

I have couple questions about ownership model between SubscriptionSubscriberPublisher. Sorry many letters :slight_smile:

  1. Subscriber (sink) will be alive since we do .store(in: &subscriptions). But Is Publisher will also guaranteed to be alive until we cancel/release subscription? Since publisher is not retained in case below, only strong reference to subscription.

Basically question is to how guarantee Subscriber will not stop working due to dealloc of the publisher? E.g. is there any need additional private strong property to publisher?

  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    api.stories()
      .receive(on: DispatchQueue.main)
      .catch { _ in Empty() }
      .sink { [weak self] result in
                self?.handle(result: result)
       }
      .store(in: &subscriptions)
  }
  1. Same question in context of combining operators (e.g. combineLatest, merge):
    Will combineLatest/merge maintain strong reference to subscriber+publishers until we cancel/release subscription? Or do we need additional private strong properties to publishers?

 override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

let storiesPublisher =
    api.stories()
      .receive(on: DispatchQueue.main)
      .catch { _ in Empty() }
      .share()

let storiesCommentsPublisher =
    api.storiesComments()
      .receive(on: DispatchQueue.main)
      .catch { _ in Empty() }
      .share()

storiesPublisher
       .map { [unowned self] in self.mapStories($0) }
       .sink { [weak self] result in
                self?.handleStories(result: result)
       }
      .store(in: &subscriptions)
storiesCommentsPublisher
       .map { [unowned self] in self.mapComments($0) }
       .sink { [weak self] result in
                self?.handleStoriesComments(result: result)
       }
      .store(in: &subscriptions)

storiesPublisher
            .combineLatest(storiesCommentsPublisher)
            .map { [unowned self] stories, comments in
                self.handleStoriesAndStoriesCommentsCombined(stories: stories, comments: comments)
            }
            .assign(to: &$state)

}
  1. Is it safe to use unowned self inside map/sink operator above? Assuming self strong referencing all subscriptions. Is there possibility for dangling state: (self is nil) and sink/map closures invoked?

Hello @icanzilb. Never mind, I found in the book following:
Both system-provided subscribers conform to Cancellable, which means that your subscription code (e.g. the whole publisher, operators and subscriber call chain) returns a Cancellable object. Whenever you release that object from memory, it cancels the whole subscription and releases its resources from memory.
So I guess Publisher is considered as resources and should be retained.

Regarding the last question with weak/unowned I found your article with in-depth explanation:

Thanks, Dzmitry.

1 Like

Yes, normal Swift memory management rules apply - the cancellables are there only to memory manage the lifetime of subscriptions; since subscriptions run asynchronously Combine needs a way to know when to release a subscription and that happens when your release the cancellable from memory (or call cancel())

1 Like