Telegramå¼ææ¶ææ¨¡å¼åæ | AIçæåç¿»è¯
以䏿¯ä»åºä¸å®é 代ç 示ä¾çæ©å±åæï¼
1. TelegramEngine å¤è§æ¨¡å¼ â æ¸ æ°çå±çº§å离
TelegramEngine ç±»æ¯æ¶æçæ ¸å¿ã宿¯ä¸ä¸ªæ£ç¡®çä¸å¸å¯¹è±¡ââåä¸çå
¥å£ç¹ï¼å°æ¯ä¸ªé¢åéè¿æåå§åå弿è¿è¡å½å空é´ååï¼
// submodules/TelegramCore/Sources/TelegramEngine/TelegramEngine.swift
public final class TelegramEngine {
public let account: Account
public init(account: Account) {
self.account = account
}
public lazy var secureId: SecureId = { return SecureId(account: self.account) }()
public lazy var peers: Peers = { return Peers(account: self.account) }()
public lazy var messages: Messages = { return Messages(account: self.account) }()
public lazy var resources: Resources = { return Resources(account: self.account) }()
public lazy var data: EngineData = {
return EngineData(accountPeerId: self.account.peerId, postbox: self.account.postbox)
}()
public lazy var preferences: Preferences = { return Preferences(account: self.account) }()
// ... 15+ 个æ´å¤é¢å
}
æ¯ä¸ªå弿齿¯ä¸ä¸ªå¤è§ï¼å
è£¹äº _internal_* 彿°ãå
Œ
± API 使ç¨å¼æçº§ç±»åï¼EngineMessageãEnginePeerãEngineMediaResourceï¼ï¼èå
é¨å®ç°ä½¿ç¨åå§ç Postbox ç±»åãè¿å°±æ¯ 45 æ³¢éæçå®é
åºç¨ï¼
// submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift
public extension TelegramEngine {
final class Messages {
private let account: Account
// å¤è§ï¼æ¸
æ°çå
Œ
±ç¾åä½¿ç¨ Engine ç±»å
public func downloadMessage(messageId: EngineMessage.Id) -> Signal<EngineMessage?, NoError> {
return _internal_downloadMessage(
accountPeerId: self.account.peerId,
postbox: self.account.postbox,
network: self.account.network,
messageId: messageId
)
|> map { message -> EngineMessage? in
return message.flatMap(EngineMessage.init) // å
è£
åå§ -> Engine
}
}
// å¤è§ï¼å§æç»å
é¨ï¼æ¡¥æ¥ç±»å
public func deleteMessagesInteractively(messageIds: [MessageId], type: InteractiveMessagesDeletionType) -> Signal<Void, NoError> {
self.account.stateManager.messagesRemovedContext.addIsMessagesDeletedInteractively(
ids: messageIds.map { id -> DeletedMessageId in
if id.namespace == Namespaces.Message.Cloud &&
(id.peerId.namespace == Namespaces.Peer.CloudUser ||
id.peerId.namespace == Namespaces.Peer.CloudGroup) {
return .global(id.id)
} else {
return .messageId(id)
}
}
)
return _internal_deleteMessagesInteractively(account: self.account, messageIds: messageIds, type: type)
}
}
}
模å¼ï¼å
Œ
±æ¹æ³ â ç±»åæ¡¥æ¥ â _internal_* 彿°ï¼é¢å Postboxï¼ãæ¶è´¹è
模åç»ä¸ import Postboxã
2. EngineMediaResource â 带æéçå£çå è£ ç±»
EngineMediaResource å
è£
äºåå§ç MediaResource åè®®ï¼ä½æä¾äºæ¾å¼çéçå£ï¼_asResource()ï¼ä¾éè¦åå§ç±»åæ¶ä½¿ç¨ï¼
// submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift
public final class EngineMediaResource: Equatable {
public struct Id: Equatable, Hashable {
public var stringRepresentation: String
public init(_ stringRepresentation: String) { self.stringRepresentation = stringRepresentation }
public init(_ id: MediaResourceId) { self.stringRepresentation = id.stringRepresentation }
}
private let resource: MediaResource // åå§ Postbox åè®®
public init(_ resource: MediaResource) { self.resource = resource }
public func _asResource() -> MediaResource { return self.resource } // éçå£
public var id: Id { return Id(self.resource.id) }
public static func ==(lhs: EngineMediaResource, rhs: EngineMediaResource) -> Bool {
return lhs.resource.isEqual(to: rhs.resource)
}
}
以ä¸å线为åç¼ç _asResource() 表示âè¿æç ´äºæ½è±¡ï¼è¯·è°¨æ
使ç¨âãå½å约å®å¨æ´ä¸ªä»£ç åºä¸ä¿æä¸è´ââ_internal_* ç¨äºé¢å Postbox ç彿°ï¼_as* ç¨äºè§£å
ã
3. EngineData â ç±»åå®å ¨çååºå¼æ°æ®è®¢é
è¿æ¯ä»£ç åºä¸æå¤æç模å¼ã宿ä¾äºä¸ä¸ªç±»åå®å ¨ãå¯ç»åç APIï¼ç¨äºè®¢é æ°æ®åºè§å¾è䏿´é² Postbox å é¨ç»èï¼
// submodules/TelegramCore/Sources/TelegramEngine/Data/TelegramEngineData.swift
public protocol TelegramEngineDataItem {
associatedtype Result
}
// å
鍿¡¥æ¥åè®®
protocol PostboxViewDataItem: TelegramEngineDataItem {
var key: PostboxViewKey { get }
func extract(view: PostboxView) -> Result
}
public extension TelegramEngine {
final class EngineData {
let accountPeerId: PeerId
private let postbox: Postbox
// æ ¸å¿ï¼å°å¤ä¸ªè§å¾é®å并为ä¸ä¸ªç»å订é
private func _subscribe(items: [AnyPostboxViewDataItem]) -> Signal<[Any], NoError> {
var keys = Set<PostboxViewKey>()
for item in items {
for key in item.keys(data: self) { keys.insert(key) }
}
return self.postbox.combinedView(keys: Array(keys))
|> map { views -> [Any] in
var results: [Any] = []
for item in items {
results.append(item._extract(data: self, views: views.views))
}
return results
}
}
// ç±»åå®å
¨éè½½ï¼1ã2ã3ã4ã5 个å
ç´ ï¼ä½¿ç¨æ£ç¡®çå
ç»ç±»å
public func subscribe<T0: TelegramEngineDataItem>(_ t0: T0) -> Signal<T0.Result, NoError> {
return self._subscribe(items: [t0 as! AnyPostboxViewDataItem])
|> map { results -> T0.Result in results[0] as! T0.Result }
}
public func subscribe<T0, T1>(_ t0: T0, _ t1: T1) -> Signal<(T0.Result, T1.Result), NoError>
public func subscribe<T0, T1, T2>(_ t0: T0, _ t1: T1, _ t2: T2) -> Signal<(T0.Result, T1.Result, T2.Result), NoError>
// ... æå¤ 5 个å
ç´
// 䏿¬¡æ§åä½
public func get<T0: TelegramEngineDataItem>(_ t0: T0) -> Signal<T0.Result, NoError> {
return self.subscribe(t0) |> take(1)
}
}
}
æ¶è´¹è 代ç ä¸çç¨æ³å¦ä¸ï¼
context.engine.data.subscribe(
EngineData.Item.Peer(id: peerId),
EngineData.Item.PeerPresence(id: peerId)
)
|> map { peer, presence -> ... in
// å®å
¨ç±»ååï¼æ é Postbox 导å
¥
}
å
é¨ç _subscribe æ¹æ³ä½¿ç¨ç±»åæ¦é¤ï¼[Any]ï¼å¤ç弿éåï¼ç¶åå
Œ
±éè½½éè¿ as! 强å¶è½¬æ¢æ¢å¤ç±»åãè¿æ¯ Swift æ³åçæéåºç¨ââå¯åæ³åï¼ä½¿ç¨ repeat each T 注éæï¼ä¼è®©å®æ´å®ç¾ï¼ä½è¿éè¦ Swift 5.9+ã
4. èªå®ä¹ååºå¼æ¡æ¶ â SSignalKit
Telegram 没æä½¿ç¨ Combine æ RxSwiftãä»ä»¬ä»å¤´æå»ºäºèªå·±çååºå¼æ¡æ¶ï¼å¹¶ä½¿ç¨ pthread_mutex ä¿è¯çº¿ç¨å®å
¨ï¼
// submodules/SSignalKit/SwiftSignalKit/Source/Signal.swift
public final class Signal<T, E> {
private let generator: (Subscriber<T, E>) -> Disposable
public init(_ generator: @escaping(Subscriber<T, E>) -> Disposable) {
self.generator = generator
}
public func start(next: ((T) -> Void)! = nil, error: ((E) -> Void)! = nil,
completed: (() -> Void)! = nil) -> Disposable {
let subscriber = Subscriber<T, E>(next: next, error: error, completed: completed)
let disposable = self.generator(subscriber)
return SubscriberDisposable(subscriber: subscriber, disposable: disposable)
}
// ä¾¿æ·æé å¨
public static func single(_ value: T) -> Signal<T, E> { ... }
public static func fail(_ error: E) -> Signal<T, E> { ... }
public static func never() -> Signal<T, E> { ... }
}
// èªå®ä¹ç®¡éæä½ç¬¦ï¼ç¨äºå½æ°å¼ç»å
infix operator |> : PipeRight
public func |> <T, U>(value: T, function: ((T) -> U)) -> U {
return function(value)
}
// 弿¥æ¡¥æ¥ï¼iOS 13+ï¼
@available(iOS 13.0, *)
public extension Signal where E == NoError {
func get() async -> T {
let disposable = MetaDisposable()
return await withCheckedContinuation { continuation in
disposable.set((self |> take(1)).startStandalone(next: { value in
continuation.resume(returning: value)
}))
}
}
}
|> 管éæä½ç¬¦è¢«å¹¿æ³ä½¿ç¨ââå®å°ä¿¡å·é¾è½¬å为å¯è¯»ç管éï¼
context.engine.data.subscribe(EngineData.Item.Peer(id: peerId))
|> map { peer -> String in peer.debugDisplayableName }
|> deliverOnMainQueue
SubscriberDisposable ä½¿ç¨ pthread_mutexï¼è䏿¯ NSLock æ os_unfair_lockï¼ââè¿æ¯ææä¸ºä¹ãpthread_mutex æ¯æå
·å¯ç§»æ¤æ§çï¼å¹¶ä¸å¨ææ Apple å¹³å°ä¸å
·æå®ä¹æç¡®çè¡ä¸ºã对 subscriber çå¼±å¼ç¨ç¡®ä¿å³ä½¿ä¿¡å·é¾è¢«åºå¼ä¹è½è¿è¡æ¸
çã
5. ValueBox â åºå±åå¨åè®®
åå¨å±æ¯ä¸ä¸ªèªå®ä¹çé®å¼åå¨åè®®ï¼ç± SQLite/sqlcipher æ¯æï¼ï¼å ·ææ¾å¼çäºå¡æ§å¶ï¼
// submodules/Postbox/Sources/ValueBox.swift
public protocol ValueBox {
func begin()
func commit()
func checkpoint()
func range(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey,
values: (ValueBoxKey, ReadBuffer) -> Bool, limit: Int)
func filteredRange(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey,
values: (ValueBoxKey, ReadBuffer) -> ValueBoxFilterResult, limit: Int)
func get(_ table: ValueBoxTable, key: ValueBoxKey) -> ReadBuffer?
func set(_ table: ValueBoxTable, key: ValueBoxKey, value: MemoryBuffer)
func remove(_ table: ValueBoxTable, key: ValueBoxKey, secure: Bool)
func exists(_ table: ValueBoxTable, key: ValueBoxKey) -> Bool
func count(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey) -> Int
// å
ç½®å
¨ææç´¢
func fullTextSet(_ table: ValueBoxFullTextTable, collectionId: String,
itemId: String, contents: String, tags: String)
func fullTextMatch(_ table: ValueBoxFullTextTable, collectionId: String?,
query: String, tags: String?, values: (String, String) -> Bool)
// ç¨äºè´¦æ·è¿ç§»çå å¯å¯¼åº
func exportEncrypted(to exportBasePath: String, encryptionParameters: ValueBoxEncryptionParameters)
}
å ³é®è®¾è®¡å³çï¼
ValueBoxKeyæ¯ç±»ååçé®ï¼äºè¿å¶æ int64ï¼ââæ²¡æå符串类åçæ··ä¹±ReadBufferè¿ååå§æé以å®ç°é¶æ·è´è¯»åMemoryBufferç¨äºåå ¥ââé¿å Data åé å¼ésecure: Boolç¨äºå é¤ââä»ç£çæ¦é¤æ°æ®ï¼èä¸ä» ä» æ¯æ è®°å é¤ValueBoxFilterResultå ·æ.acceptã.skipã.stopââæ¯ææåç»æ¢ç髿èå´æ«æ- å ¨ææç´¢æ¯ä¸çå ¬æ°ï¼èé忥éå çåè½
6. Bazel æå»ºç³»ç» â æ¨¡ååä¾èµå¾
BUILD æä»¶å±ç¤ºäº 273 ä¸ªåæ¨¡åå¦ä½è¿æ¥å¨ä¸èµ·ï¼
# Telegram/BUILDï¼æå½ï¼
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application", "ios_extension")
# å符串代ç çæ
genrule(
name = "GeneratedPresentationStrings",
srcs = [
"//build-system:GenerateStrings/GenerateStrings.py",
"Telegram-iOS/en.lproj/Localizable.strings",
],
cmd = '''
python3 $(location //build-system:GenerateStrings/GenerateStrings.py) \\
--source=$(location Telegram-iOS/en.lproj/Localizable.strings) \\
--outImplementation=$(location GeneratedPresentationStrings/Sources/PresentationStrings.m) \\
--outHeader=$(location GeneratedPresentationStrings/PublicHeaders/PresentationStrings/PresentationStrings.h) \\
--outData=$(location GeneratedPresentationStrings/Resources/PresentationStrings.data)
''',
)
# æ¬å°åï¼ä¸ºæªä½¿ç¨çè¯è¨æä¾ç©ºåæ ¹
[
genrule(
name = "Localizable_{}.strings".format(language),
outs = ["{}.lproj/Localizable.strings".format(language)],
cmd = "touch $(OUTS)",
) for language in empty_languages
]
æå»ºç³»ç»ä½¿ç¨ï¼
- æ¯ä¸ªå模å使ç¨
swift_libraryï¼ç»ç²åº¦ä¾èµè·è¸ªï¼ - 使ç¨
genruleè¿è¡ä»£ç çæï¼å符串ãæå¾ï¼ - 使ç¨
config_setting忢è°è¯/åå¸/æ©å± - 使ç¨
bool_flagå®ç°æå»ºæ¶åè½å¼å ³ï¼disableExtensionsãdisableProvisioningProfilesï¼ - Bazel ç¼åï¼
--cacheDir ~/telegram-bazel-cacheï¼ç¨äºå¢éæå»º
7. ChatController â 10,925 è¡çåºç¶å¤§ç©
ChatController.swift æ 10,925 è¡ / 624 KBãå®å¯¼å
¥äº 80 å¤ä¸ªæ¨¡åãè¿æ¯ä¸»è¦çè天çé¢ââåºç¨ç¨åºä¸æå¤æçè§å¾æ§å¶å¨ï¼
// submodules/TelegramUI/Sources/ChatController.swiftï¼å¯¼å
¥æå½ï¼
import Foundation
import UIKit
import Postbox
import SwiftSignalKit
import Display
import AsyncDisplayKit
import TelegramCore
import SafariServices
import MobileCoreServices
import Intents
import LegacyComponents
import TelegramPresentationData
import TelegramUIPreferences
// ... 70+ æ´å¤å¯¼å
¥
ChatMessageBubbleItemNodeï¼7,565 è¡ï¼éè¿ä¸ä¸ªå¯ç»åçå 容èç¹ç³»ç»å¤çæææ¶æ¯ç±»åï¼
// submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift
private struct BubbleItemAttributes {
var index: Int?
var isAttachment: Bool
var neighborType: ChatMessageBubbleRelativePosition.NeighbourType
var neighborSpacing: ChatMessageBubbleRelativePosition.NeighbourSpacing
}
æ¯ç§æ¶æ¯ç±»åï¼ææ¬ãæä»¶ãæç¥¨ã游æãå票ãå°å¾ãè系人ãèµ åã礼ç©çï¼é½æèªå·±ç ChatMessage*BubbleContentNodeââ30 å¤ä¸ªä¸é¨çå
容èç¹ç»åå°æ°æ³¡ä¸ãè¿æ¯å¤§è§æ¨¡åºç¨ççç¥æ¨¡å¼ã
8. ValueBox å å¯ â å®å ¨è®¾è®¡
public struct ValueBoxEncryptionParameters {
public struct Key {
public let data: Data
public init?(data: Data) {
if data.count == 32 { self.data = data } // 256 ä½å¯é¥
else { return nil }
}
}
public struct Salt {
public let data: Data
public init?(data: Data) {
if data.count == 16 { self.data = data } // 128 ä½çå¼
else { return nil }
}
}
public let forceEncryptionIfNoSet: Bool // å³ä½¿æªæ¾å¼é
ç½®ä¹è¿è¡å å¯
}
forceEncryptionIfNoSet æ å¿æå³çå 坿¯å¯ééåºèééæ©å å
¥çãValueBox ä¸ç exportEncrypted æ¹æ³æ¯æå å¯çè´¦æ·å¯¼åº/è¿ç§»ââä½ çæ°æ®åºæ°¸è¿ä¸ä¼ä»¥æªå å¯çå½¢å¼åå
¥ç£çã
æ¶ææ¨¡å¼æ»ç»
è¿å°±æ¯å½ä¸æ¯ç± IOI/ACM éçå¾ä¸»ç»æçå¢é设计 iOS åºç¨ç¨åºæ¶ä¼åççæ åµãå±çº§æ¸ æ°ï¼æ½è±¡æ¯ææçï¼éçå£ç¨ä¸å线å½åï¼ä»¥ä¾¿ä½ ç¥é使¶å¨æç ´è§åã
