Let me use one more post to finish the introduction of the foundation modules of the project.

Logging

The module TelegramCore provides a simple logging implementation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final class Logger {
    private let queue = Queue(name: "org.telegram.Telegram.log", qos: .utility)
    
    // `false` in AppStore build
    public var logToFile: Bool
    
    // `false` in AppStore build
    public var logToConsole: Bool
    
    // `true` in AppStore build
    public var redactSensitiveData: Bool
    
    public static var shared: Logger
    
    public func log(_ tag: String, _ what: @autoclosure () -> String)
}

// for other modules
Logger.shared.log("Keychain", "couldn't get \(key) — not current")

It supports logging into the console and file system if flags are on. The queue is used to run file writings in a non-main thread. redactSensitiveData is for other codes to decide including sensitive data in a log message or not.

1
2
3
4
5
if Logger.shared.redactSensitiveData {
    messageText = "[[redacted]]"
} else {
    messageText = message.text
}

In public releases, the settings could still be changed via the in-app debugging controller that is introduced in the last section.

For modules that don’t depend on TelegramCore, the project setups bridge functions by registeredLoggingFunctions in Network.swift. It redirects the logging calls to the shared logger from modules like MtProtoKit, Postbox, and TelegramApi.

It’s not a fancy framework that doesn’t even support logging levels. There are also many modules that don’t do logging at all or simply log via print/NSLog. It might need further clean up IMO. For example, the opened URL in a chat is logged to the system output via print in a recent commit, which doesn’t sound like a good idea.

Crash Reporting

It’s reasonable that Telegram doesn’t use 3rd-party SDKs to prevent leaks of user data. It still surprises me that the project doesn’t have any built-in crash reporting module, not even a homegrown one. After looking into the commit histories, it did integrate Hockey SDK then removed it by commit bdc0bb2 in January this year. The engineers may rely on the crash reports from AppStore to check stability issues.

Hockey SDK has been retired by Microsoft in the favor of App Center. Telegram-iOS uses an App Center API to check for updates. There is no integration with the SDK.

Disk Storage

To support data sharing between the main app, watch app, intents app extension, the project stores most data inside a group container folder, named telegram-data. Some legacy components still use the Documents folder. Below is a typical layout of telegram-data:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
telegram-data/
|-- .tempkey  // the key for sqlcipher
|-- account-0123456789/ // data for account 0123456789
|   |-- network-stats/ // for `MTNetworkUsageManagerImpl`
|   |-- notificationsKey 
|   `-- postbox/ 
|       |-- db/
|       `-- media/ // media cache
|           |-- cache/
|           |-- short-cache/
|-- accounts-metadata/ // for `AccountManager`
|   |-- atomic-state/
|   |-- db/
|   |-- guard_db/
|   |-- media/
|   `-- spotlight/
|-- accounts-shared-data/
|-- lockState.json
|-- logs/  // log files
|-- notificationsPresentationData.json
|-- temp/
|   `-- app/
|-- widget-data/
`-- widgetPresentationData.json

Besides reading and writing files directly, the project mostly uses SQLite for structured data. Two SQLite extensions are enabled: SQLCipher for full database encryption and FTS5 for full-text search. This approach is popular in other popular messengers too, such as WeChat and SignalApp.

LMDB, a BTree based transactional key-value store, is provided for a few Objective-C components: such as TGEmbedCoubPlayerView (the embedded player for coub.com), and TGMediaEditingContext that’s responsible for editing photos and videos while sending a media message.

Network Transport

Messaging and VoIP calls are the two major scenarios that need network transport. Reliable connectivity and realtime update are vital characteristics of a messenger app, it’s a fascinating challenge as the global network environment is rather complex. Some engineering tricks were invented for it and were widely applied among messenger apps, such as traffic obfuscation, hybrid endpoint discovery, domain fronting, etc. I’ll try to write more on this topic in other articles.

MTProto, the core protocol of Telegram, was designed to support multiple transport protocols. The current version of Telegram-iOS only supports TCP transport. The HTTP transport was removed in 2018. The VoIP module libtgvoip supports both UDP and TCP transports.

Telegram-iOS also leverages VoIP notifications from PushKit to receive data via Apple’s network. It’s another widely used trick that enables an app to encapsulate data in the notification payload and process it in the background without user interactions. The same behavior can’t be replicated by the normal APNS. It’s essential for some core features, like updating the unread count, retrieving new endpoints if the app can’t connect to the backend, updating live locations, etc.

As any abuse could cause a significant battery drain problem, Apple started to require apps to invoke CallKit after receiving VoIP notifications since the iOS SDK 13. But Telegram-iOS seems to survive from the new rule as it has got a special entitlement from Apple: com.apple.developer.pushkit.unrestricted-voip. The same undocumented entitlement can also be found in SignalApp.

UI Framework

Besides using AsyncDisplayKit as its core UI rendering framework, Telegram-iOS goes further and reimplements common UIKit controllers and views with it. Most UIKit components can find their counterparts inside the project: NavigationController, TabBarController, AlertController, ActionSheetController, NavigationBar, ItemListController (replacement of UITableViewController) etc. The approach is fairly reasonable, once you were bitten by the inconsistent behavior of system controllers across major iOS versions.

Off-Topic. It’s funny that most iOS engineers would eventually learn some swizzing tricks on UIKit. Somehow, reimplementing components like UINavigationController is not a trivial task as hacking the original one. One of my favorite details is how UINavigationController manages to push and pop a landscape only controller.

Regarding the UI property animation, POP is the one for legacy UI components in Objective-C, while the swift modules mostly use their own animator implementations upon CADisplayLink or CoreAnimation.

Two Lottie libraries, rlottie and lottie-ios, are built into the app to support After Effect animations. rlottie is mainly for animated stickers in thetgs format. Lottie-ios is used to load animation files from the bundled resources. It seems there is actually no need for two libraries for the same thing, lottie-ios could be replaced by rlottie.

Unit Test

There are basically no unit tests in the project.

In-App Debugging

Tapping the setting tab for 10 times could present the debug controller, in which you can tweak log settings, collect logs, try experimental UI settings, etc.

DebugController