Select simulator → Cmd+R

April 10, 2026 · View on GitHub

Mezon

Mezon iOS

Native iOS client for the Mezon messaging platform.
High-performance, real-time communication built with AsyncDisplayKit and SwiftSignalKit.

App Store iOS 13+ Swift 5.9 MIT License


Overview

Mezon iOS is the native Apple platform client for Mezon — a Live, Work, and Play platform designed for gaming communities, professional teams, and content creators. The app delivers sub-millisecond UI responsiveness through async rendering and a fully reactive data pipeline.

Tech Stack

LayerTechnology
UI RenderingAsyncDisplayKit (Texture) 3.2 — async layout & render off main thread
Declarative UIComponentFlow — SwiftUI-like component system for forms & profiles
ReactiveSwiftSignalKit — custom Signal, ValuePipe, Disposable
DatabaseSQLCipher 4.0 — encrypted SQLite via Postbox layer
NetworkingHTTP REST (Protobuf) + WebSocket (real-time)
Serializationswift-protobuf 1.35
BuildXcode 16+ · CocoaPods · Swift Package Manager
Min TargetiOS 13.0

Architecture

┌─────────────────────────────────────────────────────┐
│  AppDelegate                                         │
│  Window1 → isLoggedInSignal                          │
│    ├─ authorized   → MezonRootController (TabBar)    │
│    └─ unauthorized → LoginViewController             │
├─────────────────────────────────────────────────────┤
│  SharedAccountContext (app-wide)                      │
│  mainWindow · theme · controller factories            │
├─────────────────────────────────────────────────────┤
│  AccountContext (per-account)                         │
│  .account  → Account (Postbox + HTTP + Socket)        │
│  .engine   → MezonEngine                              │
│  session · currentUser · login/logout                 │
├─────────────────────────────────────────────────────┤
│  MezonEngine                                          │
│  .auth · .clans · .channels · .messages · .peers      │
│  .data → EngineData (typed subscribe/get)             │
├─────────────────────────────────────────────────────┤
│  Account                                              │
│  .postbox → Postbox (SQLCipher + ViewTracker)         │
│  .network → MezonHTTPClient (REST + Protobuf)         │
│  .socket  → MezonSocket (WebSocket)                   │
└─────────────────────────────────────────────────────┘

Data Flow

Network (HTTP / WebSocket)


Postbox.write { tx in tx.addMessages(...) }

    ▼  ViewTracker.replay() → incremental updates

engine.data.subscribe(Item.MessageHistory(channelId:))

    ▼  Signal<[MessageRecord], NoError>

Controller  →  stateSignal()  →  ContainerNode / ComponentHostView


ASDisplayNode (async render off main thread)

All controllers subscribe directly to reactive Postbox signals. No ViewModel layer — state lives in the controller, UI subscribes via Signal.

Project Structure

MezonChat/

├── Application/                        App lifecycle
│   ├── AppDelegate.swift               Window, auth flow, scene handling
│   └── SplashViewController.swift      Launch screen

├── Core/
│   ├── Account/                        Infrastructure layer
│   │   ├── Account.swift               Postbox + HTTP + Socket
│   │   ├── User.swift                  User model
│   │   └── Message.swift               Message model
│   │
│   ├── AccountContext/                  Dependency injection
│   │   ├── AccountContext.swift         Protocol
│   │   ├── AccountContextImpl.swift    Implementation
│   │   └── SharedAccountContext.swift   App-wide context + factories
│   │
│   ├── MezonEngine/                    Domain API layer
│   │   ├── MezonEngine.swift           Engine + lazy sub-engine registry
│   │   ├── MezonEngine+Auth.swift      Login, OTP, refresh, logout
│   │   ├── MezonEngine+Clans.swift     Clan listing + Postbox views
│   │   ├── MezonEngine+Channels.swift  Channels, DMs
│   │   ├── MezonEngine+Messages.swift  Send, list, history views
│   │   ├── MezonEngine+Peers.swift     Profiles
│   │   └── Data/                       Typed reactive Postbox access
│   │       ├── MezonEngineData.swift   subscribe() / get() API
│   │       ├── ClansData.swift
│   │       ├── ChannelsData.swift
│   │       ├── MessagesData.swift
│   │       └── PreferencesData.swift
│   │
│   ├── Postbox/                        Local persistence (SQLCipher)
│   │   ├── Core/                       Postbox, Transaction, ViewTracker
│   │   ├── Database/                   SqliteDatabase, Table
│   │   ├── Auth/                       AuthRecord, AuthTable
│   │   ├── Channel/                    ChannelRecord, ChannelTable
│   │   ├── Clans/                      ClanRecord, ClanTable
│   │   ├── Messages/                   MessageRecord, MessageTable
│   │   ├── Profile/                    ProfileRecord, ProfileTable
│   │   ├── Settings/                   SettingsTable
│   │   ├── Preferences/                PreferencesTable
│   │   └── Coding/                     PostboxCoding protocol
│   │
│   ├── Display/                        UI framework (145 files)
│   │   ├── ViewController.swift        ASDisplayNode-backed base controller
│   │   ├── NavigationBar.swift         Custom navigation bar
│   │   ├── ListView.swift              High-performance scrolling list
│   │   ├── TabBarControllerImpl.swift  Tab bar controller
│   │   └── Navigation/                 NavigationController, containers
│   │
│   ├── ComponentFlow/                  Declarative UI (34 files)
│   │   ├── Base/                       Component, CombinedComponent, Environment
│   │   ├── Components/                 Text, Image, Button, VStack, HStack,
│   │   │                               List, ScrollComponent, CardComponent
│   │   ├── Gestures/                   Tap, Pan, LongPress
│   │   ├── Host/                       ComponentHostView
│   │   └── Utils/                      ActionSlot, Color, Insets, Size
│   │
│   ├── SSignalKit/                     Reactive framework
│   │   ├── SwiftSignalKit/             Signal, Subscriber, ValuePipe, Queue
│   │   └── SSignalKit/                 Obj-C counterpart
│   │
│   ├── Extensions/                     UIColor+Theme, UIView+Layout
│   ├── Localization/                   L10n, LanguageManager
│   ├── Theme/                          ThemeManager, AppTheme
│   ├── Security/                       KeychainHelper
│   └── Utils/                          Constants, ScreenScale, AppLogger

├── Features/
│   ├── Auth/Login/                     Login form (ComponentFlow)
│   ├── Auth/VerifyOTP/                 OTP verification
│   ├── Clans/                          Clan sidebar + channel list + chat
│   ├── Main/                           HomeViewController, MezonRootController
│   ├── Messages/                       Direct messages
│   ├── Profile/                        User profile (ComponentFlow)
│   ├── SendMessageInput/               Message input bar
│   ├── Settings/                       Theme & language settings
│   └── TabBar/                         Placeholder tabs

├── Networking/
│   ├── MezonHTTPClient.swift           REST client (JSON + Protobuf)
│   ├── MezonSocket.swift               WebSocket real-time messaging
│   ├── MezonSession.swift              Session model
│   ├── MezonEnvironment.swift          Dev / Prod config
│   └── SessionRefreshManager.swift     Token refresh with retry

├── Generated/                          Protobuf generated code
│   ├── api/api.pb.swift
│   └── rtapi/realtime.pb.swift

└── Resources/
    ├── Info.plist
    ├── Assets.xcassets/
    └── Images.xcassets/

Key Patterns

Dependency Injection

Every controller receives AccountContext via init. No singletons in feature code.

let vc = ClanListViewController(context: context)

// Access through DI chain:
context.engine.clans.listClanDescs(token:)
context.account.postbox.write { tx in ... }
context.engine.data.subscribe(Item.ClanList())

Reactive Data Access

// Continuous updates from local database
context.engine.data.subscribe(
    MezonEngine.EngineData.Item.ClanList()
)
|> deliverOnMainQueue
|> start(next: { clans in
    // UI updates automatically when data changes
})

// One-shot read
context.engine.data.get(
    MezonEngine.EngineData.Item.MessageHistory(channelId: "123")
)

Controller → Signal → UI

Controllers own state and expose a reactive signal. Display nodes subscribe and re-render.

// Controller holds state, exposes signal
final class ClanListViewController: ViewController {
    private let needsReloadPipe = ValuePipe<Void>()
    func stateSignal() -> Signal<ClanListState, NoError> { ... }
}

// Node subscribes to signal
init(signal: Signal<ClanListState, NoError>, interaction: ClanListInteraction) {
    disposables.add(
        (signal |> deliverOnMainQueue).start(next: { state in
            self.state = state
            self.collectionView.reloadData()
        })
    )
}

ComponentFlow

Declarative component system for static and semi-static screens.

final class ProfileContentComponent: CombinedComponent {
    static var body: Body {
        let nameText = Child(Text.self)
        let card = Child(CardComponent.self)

        return { context in
            let name = nameText.update(
                component: Text(text: context.component.displayName, font: ..., color: ...),
                availableSize: CGSize(width: contentWidth, height: 100),
                transition: context.transition
            )
            context.add(name.position(CGPoint(x: ..., y: ...)))
            return CGSize(width: context.availableSize.width, height: totalHeight)
        }
    }
}

Performance

OptimizationDetail
Async RenderingAll UI layout and rendering happens off the main thread via AsyncDisplayKit
Incremental UpdatesPostbox ViewTracker replays only changed records to active views
Encrypted PersistenceSQLCipher with WAL mode for concurrent reads and fast writes
Lazy InitializationMezonEngine sub-engines created on first access, not at startup
Component DiffingComponentFlow skips re-render when props are equal
Signal DeduplicationdistinctUntilChanged prevents redundant UI updates
Pixel-Perfect LayoutScreenScale rounds all dimensions to device pixels
Image CachingText component renders to CGImage and caches size measurements

Getting Started

Prerequisites

  • Xcode 16.0+
  • CocoaPods (gem install cocoapods)
  • iOS 13.0+ simulator or device

Build & Run

git clone https://github.com/mezonai/mezon-ios.git
cd mezon-ios

pod install

open MezonChat.xcworkspace
# Select simulator → Cmd+R

Environment

The app connects to Mezon production by default. To switch to development:

// In AppDelegate.swift
MezonEnvironment.current = .dev   // or .prod

Dependencies

PackageVersionPurpose
Texture~> 3.2Async UI layout and rendering
SQLCipher~> 4.0Encrypted SQLite database
swift-protobuf1.35Protobuf serialization

Contributing

We welcome contributions! See the main Mezon repository for contribution guidelines.

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Make changes and ensure the project builds
  4. Submit a pull request

License

This project is licensed under the MIT License. See LICENSE for details.


Made with care by the Mezon team
Website · GitHub · App Store