Ryan
Ryan

Reputation: 6662

Make a Publisher from a callback

I would like to wrap a simple callback so that it would be able to be used as a Combine Publisher. Specifically the NSPersistentContainer.loadPersistentStore callback so I can publish when the container is ready to go.

func createPersistentContainer(name: String) -> AnyPublisher<NSPersistentContainer, Error> {
  // What goes here?
  // Happy path: send output NSPersistentContainer; send completion.
  // Not happy path: send failure Error; send completion.
}

For instance, what would the internals of a function, createPersistentContainer given above, look like to enable me to do something like this in my AppDelegate.

final class AppDelegate: UIResponder, UIApplicationDelegate {

  let container = createPersistentContainer(name: "DeadlyBattery")
    .assertNoFailure()
    .eraseToAnyPublisher()

  // ...

}

Mostly this boils down to, how do you wrap a callback in a Publisher?

Upvotes: 7

Views: 4167

Answers (3)

malhal
malhal

Reputation: 30746

NSPersistentContainer is just a convenience wrapper around a core data stack, you would be better off subscribing at the source:

NotificationCenter.default.publisher(for: .NSPersistentStoreCoordinatorStoresDidChange)

Upvotes: -2

Matej Balantič
Matej Balantič

Reputation: 1807

As one of the previous posters @Ryan pointed out, the solution is to use the Future publisher.

The problem of using only the Future, though, is that it is eager, which means that it starts executing its promise closure at the moment of creation, not when it is subscribed to. The answer to that challenge is to wrap it in the Deferred publisher:

func createPersistentContainer(name: String) -> AnyPublisher<NSPersistentContainer, Error> {
    return Deferred {
        Future<NSPersistentContainer, Error> { promise in
            let container = NSPersistentContainer(name: name)
            container.loadPersistentStores { _, error in
                if let error = error {
                    promise(.failure(error))
                } else {
                    promise(.success(container))
                }
            }
        }
    }.eraseToAnyPublisher()
}

Upvotes: 17

Ryan
Ryan

Reputation: 6662

It seems that Combine's Future is the correct tool for the job.

func createPersistentContainer(name: String) -> AnyPublisher<NSPersistentContainer, Error> {
  let future = Future<NSPersistentContainer, Error> { promise in
    let container = NSPersistentContainer(name: name)
    container.loadPersistentStores { _, error in
      if let error = error {
        promise(.failure(error))
      } else {
        promise(.success(container))
      }
    }
  }
  return AnyPublisher(future)
}

Upvotes: 10

Related Questions