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.
[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 start
can register observer closures.
[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 terminatedputError
sends an error to the error
closure and marks the subscriber terminatedputCompletion
invokes the completed
closure and marks the subscriber terminated.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:
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.
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: ActionDisposable
, MetaDisposable
, DisposableSet
, and DisposableDict
.
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