结合 ObservableObject 实现实时数据更新
概述
在动态图表开发中,实时数据更新是核心需求之一。SwiftUI 通过 ObservableObject 协议和 @Published 属性包装器提供了响应式数据流机制,使图表能够自动响应数据变化并更新界面。
关键技术
1. ObservableObject 协议
class StockData: ObservableObject {
@Published var prices: [Double] = []
@Published var timestamps: [Date] = []
}
ObservableObject协议标记的类可以通知视图其内部数据的变化@Published属性包装器自动在值改变时触发视图更新
2. 在视图中使用
struct StockChartView: View {
@ObservedObject var data: StockData
var body: some View {
Chart {
ForEach(Array(zip(data.timestamps, data.prices)), id: \.0) { timestamp, price in
LineMark(
x: .value("Time", timestamp),
y: .value("Price", price)
)
}
}
}
}
@ObservedObject属性包装器建立视图与数据模型的绑定关系
实现实时更新的三种模式
1. 定时器驱动更新
class StockData: ObservableObject {
private var timer: Timer?
func startUpdates() {
timer = Timer.scheduledTimer(
withTimeInterval: 1.0,
repeats: true
) { [weak self] _ in
self?.fetchNewData()
}
}
private func fetchNewData() {
// 模拟获取新数据
let newPrice = Double.random(in: 100...200)
prices.append(newPrice)
timestamps.append(Date())
// 保持数据量合理
if prices.count > 50 {
prices.removeFirst()
timestamps.removeFirst()
}
}
}
2. WebSocket 实时数据
class WebSocketData: ObservableObject {
private var webSocketTask: URLSessionWebSocketTask?
func connect() {
let url = URL(string: "wss://api.example.com/realtime")!
webSocketTask = URLSession.shared.webSocketTask(with: url)
webSocketTask?.resume()
receiveMessage()
}
private func receiveMessage() {
webSocketTask?.receive { [weak self] result in
switch result {
case .success(let message):
self?.processMessage(message)
self?.receiveMessage() // 继续监听下一条消息
case .failure(let error):
print("WebSocket error: \(error)")
}
}
}
}
3. Combine 框架集成
import Combine
class WeatherData: ObservableObject {
private var cancellables = Set<AnyCancellable>()
func startUpdates() {
WeatherAPI.fetchUpdates()
.receive(on: DispatchQueue.main)
.sink { [weak self] newData in
self?.updateChart(with: newData)
}
.store(in: &cancellables)
}
}
性能优化技巧
节流控制:使用
debounce避免高频更新$data .debounce(for: .seconds(0.5), scheduler: RunLoop.main) .sink { _ in /* 更新视图 */ }差异更新:仅更新变化的数据点
func updateData(newPoints: [DataPoint]) { let diff = newPoints.difference(from: dataPoints) dataPoints.apply(diff) }后台处理:将数据预处理放在后台线程
DispatchQueue.global(qos: .userInitiated).async { let processed = processRawData(rawData) DispatchQueue.main.async { self.data = processed } }
完整案例:实时心率监测图表
class HeartRateMonitor: ObservableObject {
@Published var readings: [(time: Date, rate: Int)] = []
private var timer: Timer?
func startMonitoring() {
timer = Timer.scheduledTimer(
withTimeInterval: 1.0,
repeats: true
) { [weak self] _ in
self?.simulateReading()
}
}
private func simulateReading() {
let newReading = (
time: Date(),
rate: Int.random(in: 60...120) // 模拟心率数据
)
readings.append(newReading)
// 保持最近100个读数
if readings.count > 100 {
readings.removeFirst()
}
}
}
struct HeartRateChart: View {
@ObservedObject var monitor: HeartRateMonitor
var body: some View {
Chart {
ForEach(monitor.readings, id: \.time) { reading in
LineMark(
x: .value("Time", reading.time),
y: .value("BPM", reading.rate)
)
.interpolationMethod(.catmullRom)
}
}
.chartXAxis {
AxisMarks(values: .stride(by: .minute, count: 5)) { value in
AxisGridLine()
AxisTick()
AxisValueLabel(format: .dateTime.hour().minute())
}
}
.onAppear { monitor.startMonitoring() }
}
}
常见问题解决
视图不更新:
- 确保数据模型类符合
ObservableObject - 检查
@Published属性是否被正确标记 - 确认在视图中使用
@ObservedObject而非普通属性
- 确保数据模型类符合
性能问题:
- 对于高频数据,考虑使用
Canvas替代标准 Charts - 实现数据采样(downsampling)减少渲染点数
- 对于高频数据,考虑使用
内存泄漏:
- 在
ObservableObject中正确使用[weak self] - 及时取消 Combine 订阅和定时器
- 在
