TDLibKit

repository·main·Indexed 41 days ago

https://github.com/swiftgram/tdlibkit

A native Swift wrapper for Telegram's TDLib providing high-level Swift concurrency (Async/Await) and completion handler interfaces for iOS, macOS, watchOS, tvOS, and visionOS. It features a two-tier architecture using TDLibClientManager and TDLibClient, and includes tl2swift, a tool to generate Swift structures and methods from TDLib TL-scheme files.

Tokens
2.4K
Snippets
8
Records
10
Agent score
1%

What's inside TDLibKit

  1. How TDLibClientManager and TDLibClient work together

    main

    TDLibKit uses a two-tier architecture for managing connections:

    1. TDLibClientManager: A singleton-like manager that handles the underlying td_receive loop. Crucially, you must create only one TDLibClientManager instance, as td_receive can only be called from a single thread. The manager automatically polls for updates.
    2. TDLibClient: Created via the manager. Each client instance represents a specific connection/session and provides the interface for sending requests and receiving updates.

    Updates are handled via a closure provided when creating the client.

    import TDLibKit
    
    // 1. Create the single manager
    let manager = TDLibClientManager()
    
    // 2. Create a client and handle updates
    let client = manager.createClient(updateHandler: { data, client in
        // Handle updates here
    })
  2. Generate Swift code from TDLib TL-scheme using tl2swift

    main

    The tl2swift tool parses Telegram Type Language (.tl) specification files from TDLib and generates Swift structures, enums, and methods designed for working with the TDLib JSON interface.

    To use it, you must first download the TDLib scheme (typically td_api.tl) and then run the generator via swift run.

    # 1. Download the TL-scheme
    curl https://raw.githubusercontent.com/tdlib/td/master/td/generate/scheme/td_api.tl -o td_api.tl
    
    # 2. Generate Swift sources
    swift run tl2swift td_api.tl ./output/
  3. Install TDLibKit via Swift Package Manager

    main

    To install TDLibKit in Xcode:

    1. Install the latest version of Xcode.
    2. Add https://github.com/Swiftgram/TDLibKit as an SPM dependency in Project > Swift Packages.
      • Note: This may take a while as it downloads a ~300MB zip file containing the binary from the TDLibFramework dependency.
    3. Add TDLibKit as a dependency to your target.
    4. Import the module in your code.
  4. Include TDLib version and commit in generated source headers

    main

    When running tl2swift, you can optionally provide a TDLib version and a specific commit hash. These values will be included in the header comments of the generated Swift files, which is useful for tracking compatibility with specific TDLib builds.

    # Syntax: swift run tl2swift <input_tl_file> <output_directory> <tdlib_version> <tdlib_commit>
    swift run tl2swift td_api.tl ./output/ 1.7.5 73d8fb4
  5. Execute synchronous requests

    main

    Some TDLib methods are marked as "Can be called synchronously" in the official TDLib documentation. For these specific queries, you can use the client.execute(query:) method which performs a blocking call.

    let query = SetLogVerbosityLevel(newVerbosityLevel: 5)
    
    do {
        // DTO(query) wraps the query for execution
        let result = try client.execute(query: DTO(query))
        if let resultDict = result {
            print("Response: \(resultDict)")
        } else {
            print("Empty result")
        }
    } catch {
        print("Error in SetLogVerbosityLevel request \(error.localizedDescription)")
    }
  6. Execute asynchronous requests

    main

    TDLibKit supports two patterns for asynchronous requests:

    Async/Await

    Available for iOS 13.0+, macOS 10.15+, watchOS 6.0+, tvOS 13.0+, and visionOS. This is the preferred modern Swift approach.

    Completion Handlers

    Use the completion: parameter for a closure-based approach if you are not using Swift concurrency.

    Example using getChatHistory:

    // Async/Await pattern
    do {
        let chatHistory = try await client.getChatHistory(
            chatId: chatId,
            fromMessageId: 0,
            limit: 50,
            offset: 0,
            onlyLocal: false
        )
        // Process chatHistory.messages...
    } catch {
        print("Error: \(error)")
    }
    
    // Completion Handler pattern
    try? client.getChatHistory(
        chatId: chatId,
        fromMessageId: 0,
        limit: 50,
        offset: 0,
        onlyLocal: false,
        completion: { result in
            if case .failure(let error) = result {
                print("Error: \(error.localizedDescription)")
            } else if let messages = try? result.get().messages {
                // Process messages...
            }
        }
    )
  7. Close TDLib clients properly

    main

    To ensure data integrity, you must close all active clients when the application terminates. You can do this in two ways:

    1. Per-client (Asynchronous): Call close(completion:) on a specific client.
    2. Manager-wide (Blocking): Call manager.closeClients() to close all clients managed by that instance.
    // Option 1: Per-client
    let client = manager.createClient()
    try? client.close(completion: { _ in })
    
    // Option 2: Blocking all clients via manager
    manager.closeClients()
  8. Create a TDLibClient and handle updates

    main

    Use manager.createClient(updateHandler:) to instantiate a client. The handler provides the raw Data and the TDLibClient instance. You must decode the data into an Update type to process Telegram updates.

    import TDLibKit
    
    let manager = TDLibClientManager()
    
    let client = manager.createClient(updateHandler: { data, client in
        do {
            // Decode the raw data into an Update object
            let update = try client.decoder.decode(Update.self, from: data)
            switch update {
            case .updateNewMessage(let newMsg):
                switch newMsg.message.content {
                case .messageText(let text):
                    print("Text Message: \(text.text.text)")
                default:
                    break
                }
            case .updateMessageEdited:
                break
            default:
                print("Unhandled Update \(update)")
                break
            }
        } catch {
            print("Error in update handler \(error.localizedDescription)")
        }
    })
  9. Implement a custom TDLibLogger

    main

    You can monitor "send", "receive", and "execute" operations by providing a custom implementation of the TDLibLogger protocol to the TDLibClientManager during initialization.

    import TDLibKit
    
    public final class StdOutLogger: TDLibLogger {
        let queue: DispatchQueue
        
        public init() {
            queue = DispatchQueue(label: "Logger", qos: .userInitiated)
        }
        
        public func log(_ message: String, type: LoggerMessageType?) {
            queue.async {
                var fisrtLine = "---------------------------"
                if let type = type {
                    fisrtLine = ">> \(type.description): --------------"
                }
                print("\n\(fisrtLine)\n\(message)\n---------------------------")
            }
        }
    }
    
    // Initialize manager with the custom logger
    let manager = TDLibClientManager(logger: StdOutLogger())