Combine basics: types of Publishers

Just

A publisher that emits an output to each subscriber just once and then finishes. Generic only over the output since can not fail.

let justPublisher = Just<String>("one value") //Just.init(<#T##output: _##_#>)

let cancellable = justPublisher.sink(receiveCompletion: { result in
    print(result)
}, receiveValue: { value in
    print(value)
})

let cancellable2 = justPublisher.sink(receiveCompletion: { result in
    print(result)
}, receiveValue: { value in
    print(value)
})

The output is:

one value
finished
one value
finished

The publisher emits one value to each subscriber and then finishes with no error.

Empty

A publisher that never publishes any values and optionally finishes immediately. It can be useful when we need to return a publisher that will never emit any value.

//Empty.init(completeImmediately: Bool, outputType: <#T##_.Type#>, failureType: <#T##_.Type#>)
let cancellable = Empty<String, Never>()
    .sink { value in
        print(value)
    }

No output is produced.

Fail

A Fail publisher immediately terminates with the specified error. No values were emitted.

//Fail.init(error: <#T##_#>)
//Fail.init(outputType: <#T##_.Type#>, failure: <#T##_#>)

enum AnyError: Error {
    case any
    case future
    case record
}

let cancellable = Fail(error: AnyError.any)
    .sink(receiveCompletion: { result in
        print("result:", result)
    }, receiveValue: { value in
        print(value)
    })

Output:

result: failure(__lldb_expr_20.AnyError.any)

Future

A publisher that eventually produces a single value and then finishes or fails. It’s an eager publisher, which means that the code inside the closure it’s executed even when no one it’s subscribed yet. The callback parameter is like a promise that we need to fulfill with a value or an error.

// Future.init(<#T##attemptToFulfill: (@escaping Future<_, _>.Promise) -> Void##(@escaping Future<_, _>.Promise) -> Void#>)

let futurePublisher = Future<String, Never> { callback in
    print("code executed")
    callback(.success("Some message"))
    callback(.success("Another message"))
    //callback(.failure(<#T##Never#>))
}

We can see “code executed” is printed in the console even when no one is subscribed yet. If someone is subscribed:

let cancellable = futurePublisher.sink { value in
    print(value)
}

The full output will be:

code executed
Some message

We can see after a subscription, the code in the closure is not executed again, just deliver the result computed previously. As we can see, only the first message was delivered because we can only send one result to the callback.

Deferred

 A publisher that awaits a subscription before running the supplied closure to create a publisher for the new subscriber. It’s a lazy publisher, which means the closure will not be executed until someone is subscribed. We can use this to wrap a Future publisher when we want the closure is not executed until someone subscribe.

let deferredPublisher = Deferred {
    Future<String, Never> { callback in
        print("defered code executed")
        callback(.success("Some defered message"))
    }
}

No output will be emitted until someone subscribes:

let cancellable = deferredPublisher.sink { value in
    print(value)
}

Then the output will be:

 defered code executed
 Some defered message

Record

Allow us to specify a list of values that will be emitted when someone subscribes and the completion result.

// Record.init(output: <#T##[_]#>, completion: <#T##Subscribers.Completion<_>#>)
let recordPublisher = Record<String, AnyError>(output: ["hello", "word", "another output"], completion: .failure(.record))

let cancellable = recordPublisher.sink(receiveCompletion: { result in
    print(result)
}, receiveValue: { value in
    print(value)
})

The output will be:

hello
word
another output
failure(__lldb_expr_34.AnyError.record)

Subjects (protocol)

A publisher exposes a method for outside callers to publish elements. A subject is a publisher that you can use to ”inject” values into a stream by calling its send(_:) method. It can be useful for adapting existing imperative code to the Combine model. We have two types of subjects: PassthroughSubject and CurrentValueSubject

PassthroughSubject

A PassthroughSubject drops values if there are no subscribers or its current demand is zero. Unlike CurrentValueSubject, it doesn’t have an initial value or buffer of the most recently-published element

let passthrough = PassthroughSubject<String, Never>()

passthrough.send("first value")

let cancellable = passthrough.sink { value in
    print(value)
}

passthrough.send("second value")
passthrough.send(completion: .finished)

The output will be:

second value

As we can see, the first element “first value” was dropped since no one was subscribed when the value was emitted.

CurrentValueSubject

A subject that wraps a single value and publishes a new element whenever the value changes. Need an initial value. When someone subscribes, emit the most recent value.

 //CurrentValueSubject.init(<#T##value: _##_#>)
let current = CurrentValueSubject<String, Never>("current one")

let cancellable = current.sink { value in
    print(value)
}

print("reading value:", current.value)

//To published values, we can send or set the value property
current.send("current two")
current.value = "current three"

The output will be:

 current one
 reading value: current one
 current two
 current three

We can access the current value with the value property. We have two ways of publishing a new element: using the send method or modifying the value property.