Next article No. 1, in this article we will study SSignalKit.
You can refer to the English version here:
https://hubo.dev/2020-05-11-source-code-walkthrough-of-telegram-ios-part-2/
Telegram-iOS uses Reactive programing in most of their modules. The following are the 3 main frameworks that use rx in the project:
- MTSignal: Can be considered reactive programing for Objective-C. It mainly uses mtprotokitmodule, implement MTProto – Telegram mobile protocol.
- SSignalKit: It is a descendant of MTSignal, with more complex processing and richer algorithms.
- SwiftSignalKit: similar to SSignalKit but it supports swift.
This post focuses on SwiftSignalKit to explain how to design with use cases. About Rx programming is a relatively new mindset compared to object-oriented programming that most developers already know. So you should go to its homepage to understand its algorithms in a deep, intuitive way here:
Signal
Signal
is a class to explain the concept of "change over time" – real-time event processing. It is described below:
To install Signal, it accepts 1 closure, including the data type that will spawn T, error spawn E, completion state. When it registers, the start function will allow registering 1 observer closure.
Subscriber
Subscriber
contains the logic of each observer closure with thread safety:
A subscriber will terminate when there is an error or receive the completed event. Subscriber status will not be able to receive additional:
putNext
sends new datato next
closure until the subscriber has not been terminated.putError
sends an error error tothe closure error
and marks that the subscriber has terminated.putCompletion
callsthe completed
closure function and the subscriber markup has ceased.
Operators – operators
A rich set of algorithms is defined on Signal. These characters are grouped based on their functions: Catch
, Combine
, Dispatch
, Loop
, Mapping
, Meta
, Reduce
, SideEffects
, Single
, Take
, and Timing
.
Let's take some group mapping as an example:
Like the map() function, they convert closure functions to Signal's new closure data type. The |>
to help the above |> into pipes.
The |> can be inspired by the pipeline operator of the java script world. With swift's back support, all the algorithms read visually:
Queue – queue
Queue
is the wrapper class on the GDC for queue management to use to dispatch data in Signal. There are 3 types of queues used: globalMainQueue,
globalDefaultQueue,
and globalBackgroundQueue.
There is no mechanism to limit excessive sending to queues, which we think can improve.
Disposable
Protocol Disposable
defines something that can be processed, such as canceling resources or cancelling tasks. There are 4 protocols that can cover all possible use cases: ActionDisposable,
MetaDisposable,
DisposableSet,
and DisposableDict.
Promise
The Promise
and ValuePromise
classes are built for a situation where many observers are interested in the same data source. Promise supports using Signal to update data, while ValuePromise is defined for accepting live data editing.
Use Cases
Consider some actual cases used in the project, demo for the use of SwiftSignalKit.
#1 Request Authorization
Ios applications want to access sensitive information such as contact, camera, location.. subject to permission from the user. While chatting with friends, Telegram-iOS has the feature of sending location as a message. Take a look at it authorized to get location through Signal.
The asymable steps of this authorization are described by SwiftSignalKit. The authorizationStatus
function in the DeviceAccess file.swift
returns 1 Signal to check the current authorization state:
When LocationPickerController
displays, it checks authorizationStatus and launches DeviceAccess.authrizeAccess if the permission is in a no determined state.
Signal.start returns a Disposable instance. The best way is to keep it by 1 variable and dispose it in the deinit function:
#2 Change Username
For a more complex example, telegram uses UsernameSetupController
to change the username. Usernames to create public links, so that others contact them.
The implementation must meet the following requirements:
- Controller must start with the current theme and current username. Telegram is a powerful theme system, all controllers must have themes.
- The input string needs to be validated local length and its characters.
- The name, once authenticed locally, is sent to the backend to check its availability. The number of requests must be limited when the user enters too fast.
- UI feedback needs to follow the input input. On-screen notification screen message status of username: checking, valid, invalid, available or not available. The right navigation button will be turned on when the username is available.
- When the user wants to update the username, the Indicator must be displayed to announce that the process is updating.
There are 3 data sources that may change over time: theme, current account, revision status. Themes and accounts are the basic data components of the application, so they have their own signals: SharedAccountContext.presentationData
and Account.viewTracker.peerView
. I will try to present them in another post. Let's focus on the state of change described by Signal step by step.
#1. Struct UsernameSetupControllerState
defines data with 3 components: input text input, validation status and updating flag. The functions helper provides for it to update and retrieve 1 new instance:
The state change is provided by statePromise
in ValuePromise, which also provides a concise feature to ignore repeated data updates. There is also stateValue
that holds the latest values because ValuePromise's data is not exposed. It is a common pattern in the project with promise values associated with state values. Designing internal value reads can be an improvement of ValuePromise IMO.
#3 The validation process can be implemented by piped Signal. The delay algorithm holds the request delay for 0.3 seconds. If entered too quickly, the previous requests will be cancelled by step 4:
#4 a MetaDisposable
keeps the Signals, and updates the data in statePromise and stateValue when next changes the value of TextFieldNode. When running checkAddressNameDisposable.set(), the previous function is removed by canceling the task inside the delayed function at step #3.
TextFieldNode
is a child class of ASDisplayNode and wraps of UITextField to enter text. Telegram-iOS leverages asym asymableporting from AsyncDisplayKit
to handle complex UI that becomes smooth and responsive.
#5 combineLatest algorithms
are used to combine 3 Signals to update UI controllers as they change.
Conclusion
SSignalKit is Telegram-iOS reactive programing solution. Core components, such as Signal and Promise, are implemented in a slightly different approach than other reactive frameworks. It is commonly used on modules used to connect UI and change data.
The designs that are recommended for use are closures. There are many nested closures, and make the indentation backwards. The project also uses a variety of closure actions for more flexibility. For me, it's a miracle that Telegram engineers still maintain the best Telegram-iOS code quality for debug Signals in a simple way.