Skip to content

Instantly share code, notes, and snippets.

@gordonbrander
Last active July 3, 2021 23:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gordonbrander/c937bb0eb5ce9f06a4c37a7742ef3c3a to your computer and use it in GitHub Desktop.
Save gordonbrander/c937bb0eb5ce9f06a4c37a7742ef3c3a to your computer and use it in GitHub Desktop.
import Foundation
import Combine
/// Holds cancellables returned by Publisher until the publisher completes.
///
/// Combine Publishers return a Cancellable that will automatically cancel the Publisher if the
/// Cancellable falls out of scope. Since Publishers can take some time to complete, you often
/// want to hold on to the Cancellable reference until the publisher has completed.
///
/// PublisherManager takes care of the boilerplate of holding on to Cancellables, and helps
/// you avoid the memory leak footgun of accidentally strong-referencing self in the completion handler.
///
/// The intent is to instantiate a long-lived instance of PublisherManager to manage multiple Publishers.
///
/// Publisher Cancellables are stored in a map by UUID.
/// The UUID is returned by `PublisherManager.sink`
/// You can also cancel a publisher by calling `PublisherManager.cancel` with the UUID.
final class PublisherManager {
/// Hashmap to store cancellables by ID
private var cancellables: [UUID: AnyCancellable] = [:]
/// Cancel a publisher by its id
func cancel(id: UUID) {
let value = cancellables.removeValue(forKey: id)
value?.cancel()
}
/// Similar in concept to `Publisher.sink` except that it holds on to the cancellable reference
/// until the cancellable is complete.
/// Returns a UUID that may be used with `cancel` to cancel the publisher.
@discardableResult func sink<T, E: Error> (
publisher: AnyPublisher<T, E>,
receiveValue: @escaping (T) -> Void
) -> UUID {
// Create a UUID for the cancellable.
// Store cancellable in dictionary by UUID.
// Remove cancellable from dictionary upon effect completion.
// This retains the effect pipeline for as long as it takes to complete
// the effect, and then removes it, so we don't have a cancellables
// memory leak.
let id = UUID()
let cancellable = publisher
.sink(
receiveCompletion: { [weak self] _ in
self?.cancellables.removeValue(forKey: id)
},
receiveValue: receiveValue
)
self.cancellables[id] = cancellable
return id
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment