0

just trying to implement SwiftUI and Combine in my new project. But stuck in this:

    func task() -> AnyPublisher<Int, Error> {

        return AnyPublisher { subscriber in

            subscriber.receive(Int(arc4random()))
            subscriber.receive(completion: .finished)
        }
    }

This produces the following compiler error:

Type '(_) -> ()' does not conform to protocol 'Publisher'

Why?

Update

Actually Random here is just as an example. The real code will look like this:

 func task() -> AnyPublisher<SomeCodableModel, Error> {

    return AnyPublisher { subscriber in

        BackendCall.MakeApiCallWithCompletionHandler { response, error in 

           if let error == error {

               subscriber.receive(.failure(error))
           } else {

               subscriber.receive(.success(response.data.filter))
               subscriber.receive(.finished)
           }
       }
    }
}

Unfortunately, I don't have access to BackendCall API since it is private. It's kind of pseudocode but, it pretty close to the real one.

Alex
  • 111
  • 1
  • 5

3 Answers3

2

You cannot initialise an AnyPublisher with a closure accepting a Subscriber. You can only initialise an AnyPublisher from a Publisher. If you want to create a custom Publisher that emits a single random Int as soon as it receives a subscriber and then completes, you can create a custom type conforming to Publisher and in the required method, receive(subscriber:), do exactly what you were doing in your closure.

struct RandomNumberPublisher: Publisher {
    typealias Output = Int
    typealias Failure = Never

    func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
        subscriber.receive(Int.random(in: 0...Int.max))
        subscriber.receive(completion: .finished)
    }
}

Then in your task method, you simply need to create a RandomNumberPublisher and then type erase it.

func task() -> AnyPublisher<Int, Never> {
    return RandomNumberPublisher().eraseToAnyPublisher()
}
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
1

If all you want is a single random value, use Just

fun task() -> AnyPublisher<Int, Never> {
  return Just(Int.random(in: 0...Int.max)).eraseToAnyPublisher()
}

Sidenote: don't use Int(arc4random()) anymore.

Procrastin8
  • 4,193
  • 12
  • 25
0

You're likely better off wrapping this in a Future publisher, possibly also wrapped with Deferred if you want it to response when subscriptions come in. Future is an excellent way to wrap external async API calls, especially ones that you can't fully control or otherwise easily adapt.

There's an example in Using Combine for "wrapping an async call with a Future to create a one-shot publisher" that looks like it might map quite closely to what you're trying to do.

If you want it to return more than a single value, then you may want to compose something out of PassthoughSubject or CurrentValueSubject that gives you an interface of -> AnyPublisher<YourType, Error> (or whatever you're looking for).

heckj
  • 7,136
  • 3
  • 39
  • 50