Introduction

T

elegram-iOS uses reactive programming in most modules. There are three frameworks to achieve reactive functions inside the project:

  • [MTSignal](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/MtProtoKit/Sources/MTSignal.m): it might be their first attempt for a reactive paradigm in Objective-C. It’s mainly used in the module MtProtoKit, which implements MTProto, Telegram’s mobile protocol.
  • [SSignalKit](https://github.com/TelegramMessenger/Telegram-iOS/tree/master/submodules/SSignalKit/SSignalKit): it’s a descendant of MTSignal for more general scenarios with richer primitives and operations.
  • [SwiftSignalKit](https://github.com/TelegramMessenger/Telegram-iOS/tree/master/submodules/SSignalKit/SwiftSignalKit): an equivalent port in Swift.

This post focuses on SwiftSignalKit to explain its design with use cases.

Design

Signal

[Signal](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/SSignalKit/SwiftSignalKit/Source/Signal.swift#L41) is a class that captures the concept of “change over time”. Its signature can be viewed as below:

To set up a signal, it accepts a generator closure which defines the ways to generate data(<T>), catch errors(<E>), and update completion state. Once it’s set up, the function startcan register observer closures.

Subscriber

[Subscriber](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/SSignalKit/SwiftSignalKit/Source/Subscriber.swift) has the logics to dispatch data to each observer closure with thread safety consideration.

A subscriber is terminated when an error occurred or it’s completed. The state can not be reversed.

  • putNext sends new data to the next closure as long as the subscriber is not terminated
  • putError sends an error to the error closure and marks the subscriber terminated
  • putCompletion invokes the completed closure and marks the subscriber terminated.

Operators

A rich set of operators are defined to provide functional primitives on Signal. These primitives are grouped into several categories according to their functions: [Catch](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Catch.swift)[Combine](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift)[Dispatch](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Dispatch.swift)[Loop](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Loop.swift)[Mapping](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Mapping.swift)[Meta](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Meta.swift)[Reduce](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Reduce.swift)[SideEffects](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/SSignalKit/SwiftSignalKit/Source/Signal_SideEffects.swift)[Single](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Single.swift)[Take](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Take.swift), and [Timing](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Timing.swift). Let’s take several mapping operators as an example:

The operator like map() takes a transformation closure and returns a function to change the data type of a Signal. There is a handy |>operator to help chain these operators as pipes:

The operator |>might be inspired by the proposed pipeline operator in the JavaScript world. By the trailing closure support from Swift, all operators can be pipelined with intuitive readability:

Queue

The class [Queue](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/SSignalKit/SwiftSignalKit/Source/Queue.swift) is a wrapper over GCD to manage the queue used to dispatch data in a Signal. There are three preset queues for general use cases: globalMainQueue, globalDefaultQueue, and globalBackgroundQueue. There is no mechanism to avoid overcommit to queues, which I think could be improved.

Disposable

The protocol [Disposable](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/SSignalKit/SwiftSignalKit/Source/Disposable.swift) defines something that can be disposed of. It’s usually associated with freeing resources or canceling tasks. Four classes implement this protocol and could cover most use cases: ActionDisposableMetaDisposableDisposableSet, and DisposableDict.

Promise

The classes [Promise](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/SSignalKit/SwiftSignalKit/Source/Promise.swift) and [ValuePromise](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/SSignalKit/SwiftSignalKit/Source/Promise.swift#L82) are built for the scenario when multiple observers are interested in a data source. Promise supports using a Signal to update the data value, while ValuePromise is defined to accept the value changes directly.

#ios #telegram #reactive-programming

Source Code Walkthrough of Telegram-iOS Part 2: SSignalKit
3.35 GEEK