Hallo vloeren en plafonds, Leo hier. Today we will talk about how useful could be for your SwiftUI views the use of Combine’s scan and reduce Operators.

Combine has a lot of operators and is what I think that makes it so powerful. You can express really complex logic just by chaining simple operators. I know that you can use combine for network requests but I think this is a kinda of a stretch for it.

Don’t get me wrong, I think is really cool to use things like this:

let url = URL(string: \(urlString))!

URLSession.shared.dataTaskPublisher(for: url)
  .map(\.data)
  .decode(type: YourResponse.self, decoder: JSONDecoder())
  .mapError { // your error mapping here too }
  .sink(receiveCompletion:, // handle completion  
        receiveValue: // handle values
       )
  .eraseToAnyPublisher()

However, I think creating a publisher that will publish only one value is, FOR ME, an overkill. In my head is just like saying “We will create a factory that will produce only ONE item”. Well, so why do you have a factory then?

Things become even harder for the Combine framework when async/await appeared . And even more after async streams.

I have a hypothesis that is because developers in general were using combine to solve really simple asynchronous communications. And that’s completely fine. The only thing is that now we have better options async/await to do that. I don’t think Combine will die anytime soon.

In fact, I think that Combine is an amazing framework and has a really good set of tools to answer a very specific set of problems.

For example, we have operators that can handle 2 or more other data streams. And you have a bunch of them. You can use the merge operator to combine two streams into one, or use combineLatest to emit values from both streams as soon as they arrive, or even can use Zip to publish values when all of your streams published the values. You have plenty of options here.

Talking about options, one thing that I found very interesting is how different the same layout can be created in code. You can have infinite versions of the code that generate the exact same screen. This is amazing because this gives us the opportunity to have our creativity flourish. One thing that is exactly about that is the SwiftUI ViewThatFits struct. With it, you can create a lot of different designs for the same View without having to calculate the size of anything by yourself.

Well that said, no more talking. Let’s code! But first…

 

Illustration of the Day

Today is not a painting, but an XX-century illustration made by Arthur Rackham named “‘Mine is a long and sad tale!’ said the Mouse, turning to Alice and sighing“.

Arthur Rackham was born on 19 September 1867 and passed away on 6 September 1939. was an English book illustrator. He is recognized as one of the leading figures during the Golden Age of British book illustration. His work is noted for its robust pen and ink drawings, which were combined with the use of watercolor, a technique he developed due to his background as a journalistic illustrator.

I chose this illustration because we are reducing values and in Alice in Wonderland’s story she drinks a magical potion that reduces her height and she becomes small — got it?

 

The Problem – How Manipulate and Aggregate Values from Streams in Combine?

You need to create an ongoing sum of a stream and in the end sum all of the ongoing sums into one final answer.

The situation that we will explore is the following: Imagine that you are building a game and each player can win points. As you don’t know who will be the next one to make points you can just wait and then calculate the right score for the game. Also at the end of several games, you want to know who is the final winner, calculating all the game results into one final winner.

There are infinite ways to solve this problem but today we will exercise our Swift Combine Framework muscles and solve it using scan and reduce operators.

But what are those operators?

 

Understanding Swift Combine’s scan Operator

In Swift’s Combine framework, the scan operator acts as a powerful accumulator, processing values over time. At its core, it parallels the `reduce` function from Swift’s standard library. However, while reduce  condenses sequences into a single result, scan  works on publishers, emitting a continuous stream of accumulated values.

To utilize scan, provide an initial result and a closure. This closure accepts the accumulated value and the next incoming value, processing them to yield a new accumulated result. As each new value flows in, subscribers receive an updated accumulated result.

Check the example below:

 

import Combine

let numbers = [1, 2, 3, 4, 5].publisher

let subscription = numbers
    .scan(0) { accumulatedValue, newValue in
        return accumulatedValue + newValue
    }
    .sink { print($0) }

You can copy and paste to your playground and test it!

This code outputs progression: `1`, `3`, `6`, `10`, `15`.

And demonstrates the evolving sum of numbers processed so far. Before we dive into the game example, let’s study the reduce operator.

 

Diving into Swift Combine’s reduce Operator

Swift Combine’s reduce operator is a culmination tool that processes all emitted values from a publisher to yield a single, consolidated result. Drawing parallels with the traditional reduce from Swift’s standard library, Combine’s variant waits until the publisher completes before yielding its result.

To harness the power of reduce operator, you supply an initial result and a closure. This closure is invoked with the accumulated value and the next value from the publisher. Once all values have been processed and the publisher completes, the final accumulated value is emitted to subscribers.

Check the sample below:

import Combine

let numbers = [1, 2, 3, 4, 5].publisher

let subscription = numbers
    .reduce(0) { accumulatedValue, newValue in
        return accumulatedValue + newValue
    }
    .sink { print($0) }

The code outputs: `15` .

The single emitted value represents the sum of all numbers in the sequence.

After this little introductory explanation about scan and reduce operators, the time for the game code sample has come!

 

Game Engine Sample Powered By Combine’s Scan and Reduce Operators

how to use combine scan and reduce operator in SwiftUI

The example will be running in the new shiny Xcode 15 beta 5. So if the example below is not running is because you are using an older iOS version.

Create a new SwiftUI project and follow the tutorial below.

First, let’s create the view model code and try to understand it.

struct Score {
  let player1: Int
  let player2: Int
}

@Observable
final class GameViewModel {
  var score: Score
  var finalWinner: String? = nil
  private var finalScores = [Score(player1: 0, player2: 1),
               Score(player1: 2, player2: 1),
               Score(player1: 3, player2: 2),
               Score(player1: 0, player2: 3),
               Score(player1: 4, player2: 1)]
  private var playerPointSubject = PassthroughSubject<Int,Never>()
  private var cancelBag = Set<AnyCancellable>()


  internal init(score: Score) {
    self.score = score

    playerPointSubject
      .scan(Score(player1: 0, player2: 0)) { [weak self] currentScore, player in
        guard let self else { return Score(player1: 0, player2: 0)
        }
        self.score = self.updateScore(player: player)
        return self.score
      }
      .sink { value in
        print("Scan: the value is \(value)")
      }
      .store(in: &cancelBag)
  }


  func score(player: Int) {
    playerPointSubject.send(player)
  }

  private func updateScore(player: Int) -> Score {
    if player == 1 {
      return Score(player1: score.player1 + 1,
            player2: score.player2)
    } else if player == 2 {
      return Score(player1: score.player1,
            player2: score.player2 + 1)
    } else {
      fatalError()
    }
  }

  func finishGame() {
      finalScores
        .publisher
        .reduce(0) { currentValue, score in
          if score.player1 > score.player2 {
            currentValue + 1
          } else if score.player1 < score.player2 {
            currentValue - 1
          } else {
            currentValue
          }
        }
        .map({ finalWinner in
          if finalWinner > 0 {
            return "Player 1 Won!"
          } else if finalWinner < 0 {
            return "Player 2 Won!"
          } else {
            return "Deuce!"
          }
        })
        .sink { [weak self] value in
          print("REDUCE: the value is \(value)")
          self?.finalWinner = value
        }
        .store(in: &cancelBag)
  }
}

In our view model, we have two basic functions. One is when a player scores a point, and the other is to finish the game by summing up all the scores.

In this example, we are using a property with finalScores as the data source for the final scores, but you can imagine that will be something that could emit values over time as the matches happen. 

The difference in use is very clear in the example above regarding scan and reduce operators. While  we are using the scan operator to constantly publishes new aggregated values as they come in our Int stream. We are starting it with an empty score and for each new value that comes we update with a new instance of score with the aggregated value. Pretty simple and powerful right?

And the reduce operator is just used to calculate the final winner when all the scores were published. In our example, they is just a property with all the scores but you can imagine that being populated in runtime.

Now let’s implement the SwiftUI view: 

struct ContentView: View {
  @State var gameViewModel = GameViewModel(score: Score(player1: 0, player2: 0))

  var body: some View {
    VStack {
      Text("Player 1 Score: \(gameViewModel.score.player1)")
        .font(.title)
      Text("Player 2 Score: \(gameViewModel.score.player2)")
        .font(.title)
      HStack {
        Button("Random Player Scores") {
          gameViewModel.score(player: Int.random(in: 1...2))
        }

        Button("Finish Game") {
          gameViewModel.finishGame()
        }
      }.buttonStyle(.borderedProminent)


      if let finalWinner = gameViewModel.finalWinner {
        Text("\(finalWinner)")
      } else {
        Text("Wait for the final Winner")
      }
    }
    .padding()
  }
}

#Preview {
  ContentView()
}

Here is a very simple code just to test our game engine. We have two texts that updates automatically when new values of the current score happens and two buttons. The first button add points to a random player and the second button emulates what would be the sum of all games played to determine the winner.

The full code is below: 

//
//  ContentView.swift
//  CombineScanReduceTutorial
//
//  Created by Leonardo Maia Pugliese on 20/08/23.
//

import SwiftUI
import Combine

struct Score {
  let player1: Int
  let player2: Int
}

@Observable
final class GameViewModel {
  var score: Score
  var finalWinner: String? = nil
  private var finalScores = [Score(player1: 0, player2: 1),
               Score(player1: 2, player2: 1),
               Score(player1: 3, player2: 2),
               Score(player1: 0, player2: 3),
               Score(player1: 4, player2: 1)]
  private var playerPointSubject = PassthroughSubject<Int,Never>()
  private var cancelBag = Set<AnyCancellable>()


  internal init(score: Score) {
    self.score = score

    playerPointSubject
      .scan(Score(player1: 0, player2: 0)) { [weak self] currentScore, player in
        guard let self else { return Score(player1: 0, player2: 0)
        }
        self.score = self.updateScore(player: player)
        return self.score
      }
      .sink { value in
        print("Scan: the value is \(value)")
      }
      .store(in: &cancelBag)
  }


  func score(player: Int) {
    playerPointSubject.send(player)
  }

  private func updateScore(player: Int) -> Score {
    if player == 1 {
      return Score(player1: score.player1 + 1,
            player2: score.player2)
    } else if player == 2 {
      return Score(player1: score.player1,
            player2: score.player2 + 1)
    } else {
      fatalError()
    }
  }

  func finishGame() {
      finalScores
        .publisher
        .reduce(0) { currentValue, score in
          if score.player1 > score.player2 {
            currentValue + 1
          } else if score.player1 < score.player2 {
            currentValue - 1
          } else {
            currentValue
          }
        }
        .map({ finalWinner in
          if finalWinner > 0 {
            return "Player 1 Won!"
          } else if finalWinner < 0 {
            return "Player 2 Won!"
          } else {
            return "Deuce!"
          }
        })
        .sink { [weak self] value in
          print("REDUCE: the value is \(value)")
          self?.finalWinner = value
        }
        .store(in: &cancelBag)
  }
}

struct ContentView: View {
  @State var gameViewModel = GameViewModel(score: Score(player1: 0, player2: 0))

  var body: some View {
    VStack {
      Text("Player 1 Score: \(gameViewModel.score.player1)")
        .font(.title)
      Text("Player 2 Score: \(gameViewModel.score.player2)")
        .font(.title)
      HStack {
        Button("Random Player Scores") {
          gameViewModel.score(player: Int.random(in: 1...2))
        }

        Button("Finish Game") {
          gameViewModel.finishGame()
        }
      }.buttonStyle(.borderedProminent)


      if let finalWinner = gameViewModel.finalWinner {
        Text("\(finalWinner)")
      } else {
        Text("Wait for the final Winner")
      }
    }
    .padding()
  }
}

#Preview {
    ContentView()
}

And that’s it for today!

 

Summary – Handling Aggregated Values in Combine Streams using Scan and Reduce

Let’s wrap up our discussion on Combine’s scan and reduce operators.

These operators are exceptionally potent tools in the Combine framework, enabling us to aggregate and manipulate streams of data with precision and flexibility.

While scan provides a continuous stream of accumulated values, reduce consolidates emitted values from a publisher into a single result.

Using these operators, we constructed a game engine that capably manages player scores and calculates the overall winner, showcasing their real-world utility.

It’s crucial to remember that while new programming patterns like async/await are emerging, Combine remains a highly relevant tool, especially when dealing with complex data streams. Developers should explore and leverage the strengths of both to ensure they’re employing the best tool for their specific needs.

Just as Arthur Rackham masterfully combined pen and ink with watercolors, marrying the strengths of Combine with other emerging tools can yield powerful results in your applications.

Fellow Apple Developers, that’s all.

I hope you liked reading this article as much as I enjoyed writing it. If you want to support this blog you can Buy Me a Coffee or say hello on Twitter. I’m available on LinkedIn or send me an e-mail through the contact page.

You can likewise sponsor this blog so I can get my blog free of ad networks.

Thanks for the reading and… That’s all folks.

Image credit: Featured Painting