Creating simple custom operators is very straightforward in Combine.

As long as your custom operator can be expressed via combining existing operators you don’t need to create a new publisher type - you can simply extend the Publisher protocol with your own custom operator.

Let’s try quickly creating one now.

Extending Publisher

You can extend Publisher like any other protocol in Swift. For this post I’ll create a debugging operator that writes logs to the system console.

Let’s start by adding the operator method:

1
2
3
4
5
6
7
import os

extension Publisher {
  func os_log(message: String = "") 
    -> Publishers.HandleEvents<Self> {

}

First of all I need to import the os module which, beside other APIs, offers access to the system log.

My new operator is called os_log (just like the system os_log(...) function I’m gonnna use). It takes an optional text I’ll be prepending to the log message and the trick here is the correct return type. Since the operator is going to use handleEvents(...) to do my bidding it’ll return a result of Publishers.HandleEvents(Self).

Each operator is implemented in a separate type in the Publishers enumeration. The operator method on Publisher, you use to chain operators, returns an instance of its corresponding publisher type.

For example if I were to use a map(...) to modify somehow the emitted value the return type of my operator would’ve been Publishers.Map<Self>. (E.g. a Map instance wrapping the upstream publisher). And finally in case I had a custom publisher type my operator would have returned Publishers.MyCustomPublisher<Self>.

For the operator body I’ll just use handleEvents(...) and log the upstream values:

1
2
3
4
handleEvents(receiveOutput: { value in
  os.os_log("[%@] value: %@", message, 
    String(describing: value))
})

With that my little logging operator is finished and I can give it a try:

1
2
3
4
5
Timer.publish(every: 1, on: .main, 
  in: .common)
  .autoconnect()
  .os_log(message: "timer")
  .sink(receiveValue: { _ in })

I subscribe to a timer publisher and use my new os_log operator to log to the system logs.

Now I can fire up macOS’s Console.app and check for my own logs - neat!

Console.app showing the custom operator log messages

Where to go from here?

To learn more about Combine check out the Combine book! Combine: Asynchronous programming with Swift - this is where you can see all updates, discuss in the website forums, and more.