Combine: Working with Filters

Combine: Working with Filters

With these short reference code snippets, you will be able to use filter operators when configuration publishers in Combine.

This brief overview will demonstrate some basic features that may come in handy when working with publishers in Combine, Apple's framework to handle asynchronous events by combining event-processing operators. The Publisher protocol declares a type that transmits a sequence of values over time that subscribers can receive as input by adopting the Subscriber protocol.

Using a sequence as values to publish, Combine allows typical operators available in Swift to shape the values that will be published or received. For example, publishers can be used with filters.

In this example, the sequence of numbers is filtered to only sink values that match the condition of being divisible by 2.

import Foundation
import Combine

let numbers = (1...20).publisher

numbers.filter { $0 % 2 == 0 }
       .sink {
           print($0)
       }

There are many other ways to filter content, e.g. by removing duplicates in a String. In this example, .removeDuplicates() removes duplicated Strings, but only if they follow in succession.

let words = "apple apple apple lemon strawberry apple apple mango watermelon apple".components(separatedBy: " ").publisher

words
    .removeDuplicates()
    .sink {
        print($0)
    }

You can also use compactMap to filter arrays, for example, to filter out array elements that can be converted to a Float, like in this example:

let strings = ["a","1.24","b","3.45","6.7"].publisher
    .compactMap{ Float($0) }
    .sink {
        print($0)
    }

You can also use prefix to limit the sequence to the prefix condition and ignore any succeeding values, for example:

let prefixNumbers = (1...10).publisher

prefixNumbers.prefix(2).sink {
    print($0)
}

prefixNumbers.prefix(while: { $0 < 3 }).sink {
    print($0)
}

There are also various convenience filters, such as first or last to only sink the first or last element of a sequence, such as in this example:

let moreNumbers = (1...9).publisher

moreNumbers.first(where: { $0 % 2 == 0 })
    .sink {
        print($0)
}

let evenMoreNumbers = (1...9).publisher

evenMoreNumbers.last(where: { $0 % 2 == 0 })
    .sink {
        print($0)
}

You can also drop elements in the sequence, e.g. dropping the first element or all elements while certain conditions are met, for example:

let tenNumbers = (1...10).publisher

tenNumbers.dropFirst(8)
    .sink {
        print($0)
}

tenNumbers.drop(while: { $0 % 3 != 0})
    .sink{
        print($0)
    }

You could use this mechanism to drop output from a publisher until a specific output is received. In this case, the output from taps is dropped until isReady sends.

let isReady = PassthroughSubject<Void, Never>()
let taps = PassthroughSubject<Int, Never>()

taps.drop(untilOutputFrom: isReady).sink {
    print($0)
}

(1...10).forEach { n in
    taps.send(n)
    
    if n == 3 {
        isReady.send()
    }
}

There are many other ways you can use filters with Combine and the beauty is that you can also use them in combination, chaining various filters to reach the results that you are looking for.

For example, if you would like to skip the first 20 values in a sequence, then use the next 30 values and filter them to be only even numbers, the filter operations could look like this:

let filterAllNumbers = (1...100).publisher

filterAllNumbers.dropFirst(20)
    .prefix(30)
    .filter { $0 % 2 == 0 }
    .sink {
    print($0)
}

Where to go next?

If you are interested in knowing more about working with Combine and the various operators available to shape the sequence of values send by publishers and received by subscribers, check our other articles on Combine that will be released over the next days and weeks.

They will cover how to work with filters, sequence, and transformation operators or timers, how to use the dataTaskPublisher, how to combine publishers as well as how to debug Combine messages, and much more.