DEV Community

Sebastien Lato
Sebastien Lato

Posted on

SwiftUI Real-Time Data Architecture (WebSockets, Streams, Consistency)

Real-time features sound simple:

“Just update the UI when data changes.”

In reality they cause:

  • race conditions
  • duplicate updates
  • UI flicker
  • inconsistent state
  • impossible-to-debug bugs

This post shows how to design real-time data pipelines in SwiftUI that stay:

  • consistent
  • predictable
  • performant
  • testable

This is how chat apps, live dashboards, and collaborative tools actually work.


🧠 The Core Principle

Real-time data is a stream, not a replacement.

Never overwrite your state blindly.


🧱 1. Single Source of Truth

final class LiveStore<T>: ObservableObject {
    @Published private(set) var value: T
}
Enter fullscreen mode Exit fullscreen mode

All updates flow through this store.


🌐 2. Event Stream Layer

protocol EventStream {
    func connect()
    func disconnect()
    var events: AsyncStream<ServerEvent> { get }
}
Enter fullscreen mode Exit fullscreen mode

Your UI never talks to sockets directly.


🔁 3. Reducer-Based Merging

func reduce(current: State, event: ServerEvent) -> State
Enter fullscreen mode Exit fullscreen mode

Never do:

state = newServerState
Enter fullscreen mode Exit fullscreen mode

Merge instead.


🧭 4. Conflict Handling

If:

  • local edits exist
  • server pushes arrive

You must reconcile.

Example:

case .remoteUpdate(let serverValue):
    if state.hasLocalChanges {
        state.queue(serverValue)
    } else {
        state.value = serverValue
    }
Enter fullscreen mode Exit fullscreen mode

🧬 5. Backpressure Control

Throttle UI updates:

for await event in stream.events.throttle(seconds: 0.2) {
    apply(event)
}
Enter fullscreen mode Exit fullscreen mode

Avoid 60 updates per second.


🧪 6. Testing Streams

let stream = MockStream(events: [.add, .remove])
Enter fullscreen mode Exit fullscreen mode

Assert:

  • final state
  • no duplicates
  • no race bugs

⚠️ 7. Avoid These

  • overwriting state
  • mutating in views
  • direct socket calls
  • no buffering
  • no ordering

🧠 Mental Model

Socket
 → Event Stream
   → Reducer
     → Store
       → UI
Enter fullscreen mode Exit fullscreen mode

🚀 Final Thoughts

Real-time apps fail when data is treated as state.

Treat it as a stream of intentions, and your UI stays stable.

Top comments (0)