Combine Framework

 

 A Practical Introduction with UIKit

Dimitri James Tsiflitzis

CocoaheadsSKG

Understanding what Functional Reactive Programming (FRP) is

  • Data flows unidirectionally and automatically through subscriptions.
  • Has the ability to map one dataflow into another
  • Useful in applications that have data that changes over time.

What is Combine?
 

  • The Combine framework can be compared to frameworks like RxSwift.

 

  • It allows you to write functional reactive code by providing a declarative Swift API.

 

  • Functional Reactive Programming (FRP) languages allow you to process values over time.

 

  • Examples of these kinds of values include network responses, user interface events, and other types of asynchronous data.

Use cases

Network requests.

 

  • Usually, you would pass a completion closure to your request which is then executed when the request is finished.
  • In FRP, the method you call to make a request would return a publisher that will publish a result once the request is finished.
  • If you want to transform the result of the network request, or maybe chain it together with another request, your code will typically be easier to reason about than a heavily nested tree of completion closures spaghetti.

Use cases

UI

 

  • If you have a label that displays the value of a slider,
  • Use FRP to push the value of your slider through a stream, or publisher which will then send the new value of the slider to all subscribers of the stream, which could be the label that shows the slider value, or anything else.

Benefits of using the Combine framework

  • Simplified asynchronous code - no more callback hells
  • Declarative syntax - easier to read and maintain code
  • Composable components - composition over inheritance & reusability
  • Cancellation support - it was always an issue with Promises
  • Multithreading - you don't have to worry about it (that much)
  • Built-in memory management - no dispose bags

The basic principles of Combine

Publishers and Subscribers

  • Publishers are the same as Observables in RxSwift
  • Subscribers are the same as Observers  in RxSwift

Publisher exposes values that can change, on which a subscriber subscribes to receive all those updates.

The Foundation framework and Combine

  • A URLSessionTask Publisher that publishes the data response or request error
  • Operators for easy JSON decoding
  • A Publisher for a specific Notification.Name that publishes the notification
🤝
 

The basic principles of Combine

Combine = Publishers + Subscribers + Operators

What is Publisher

Publishers sends sequences of values over time to one or more Subscribers.

 

protocol Publisher {
    associatedtype Output
    associatedtype Failure : Error
    func receive<S>(subscriber: S) where S : Subscriber, 
    Self.Failure == S.Failure, 
    Self.Output == S.Input
}

A publisher can send values or terminate with either success or error.

What is Subscriber

Subscriber receives values from a publisher.

 

public protocol Subscriber : CustomCombineIdentifierConvertible {
    associatedtype Input
    associatedtype Failure : Error

    func receive(subscription: Subscription)
    func receive(_ input: Self.Input) -> Subscribers.Demand
    func receive(completion: Subscribers.Completion<Self.Failure>)
}

A subscriber can receive a value of type Input or a termination event with either success or Failure .

Connecting Publisher to Subscriber

[1, 2, 3]
  .publisher
  .sink(receiveCompletion: { completion in
    switch completion {
    case .failure(let error):
      print("Something went wrong: \(error)")
    case .finished:
      print("Received Completion")
    }
  }, receiveValue: { value in
    print("Received value \(value)")
  })

Connecting Publisher to Subscriber

var subscription: AnyCancellable?

func subscribe() {
  let notification = UIApplication.keyboardDidShowNotification
  let publisher = NotificationCenter.default.publisher(for: notification)
  subscription = publisher.sink(receiveCompletion: { _ in
    print("Completion")
  }, receiveValue: { notification in
    print("Received notification: \(notification)")
  })
}

subscribe()
NotificationCenter.default.post(Notification(name: UIApplication.keyboardDidShowNotification))

Keeping track of subscriptions

Connecting Publisher to Subscriber

// start manually
let timerPublisher = Timer.publish(every: 1.0, on: RunLoop.main, in: .default)
cancellable = timerPublisher
.sink {
    print($0)
}
// start publishing time
let cancellableTimerPublisher = timerPublisher.connect()
// stop publishing time
//cancellableTimerPublisher.cancel()

// cancel subscription
//cancellable?.cancel()

You can use Combine to get periodic time updates through a publisher:

Connecting Publisher to Subscriber

subscription?.cancel()
NotificationCenter
   .default
   .post(Notification(name: UIApplication.keyboardDidShowNotification))

Keeping track of subscriptions

Transforming publishers

let publisher = NotificationCenter.default
  .publisher(for: notification)
  .map { (notification) -> CGFloat in
    guard let endFrame = notification
    .userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue 
    else {
      return 0.0
    }

    return endFrame.cgRectValue.height
}

Transforming publishers

The rules of a subscription

 

  • A subscriber can only have one subscription
  • Zero or more values can be published
  • At most one completion will be called

What are Subjects

Subject is a special kind of Publisher that can insert values, passed from the outside, into the stream. Subject’s interface provides three different ways of sending elements:

public protocol Subject : AnyObject, Publisher {
    func send(_ value: Self.Output)
    func send(completion: Subscribers.Completion<Self.Failure>)
    func send(subscription: Subscription)
}

What are Subjects

Combine has two built-in subjects: PassthroughSubject and CurrentValueSubject.

// 1
let subject = PassthroughSubject<String, Never>()
// 2
subject.sink(receiveCompletion: { _ in
    print("finished")
}, receiveValue: { value in
    print(value)
})
// 3
subject.send("Hello,")
subject.send("World!")
subject.send(completion: .finished) // 4
Hello,
World!
finished

What are Subjects

CurrentValueSubject starts with an initial value and publishes all it’s changes. It can return it’s current value via the value property.

// 1
let subject = CurrentValueSubject<Int, Never>(1)
// 2
print(subject.value)
// 3
subject.send(2)
print(subject.value)
// 4
subject.sink { value in
    print(value)
}
1
2
2

@Published usage to bind values to changes

This keyword is a property wrapper and adds a Publisher to any property.

final class FormViewController: UIViewController {
    
    @Published var isSubmitAllowed: Bool = false

    @IBOutlet private weak var acceptTermsSwitch: UISwitch!
    @IBOutlet private weak var submitButton: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        $isSubmitAllowed
           .receive(on: DispatchQueue.main)
           .assign(to: \.isEnabled, on: submitButton)
    }

    @IBAction func didSwitch(_ sender: UISwitch) {
        isSubmitAllowed = sender.isOn
    }
}

Debugging Combine streams

  • print() to print log messages for all publishing events
  • breakpoint() which raises the debugger signal when a provided closure needs to stop the process in the debugger
  • breakpointOnError() which only raises the debugger upon receiving a failure

In summary

  • Learn the basic principles
  • Watch the videos
  • Practice for a few months

Ευχαριστούμε 🎈

Combine Framework

By tsif

Combine Framework

  • 210