设计 IM 客户端
问题
如何设计一个 iOS IM 即时通讯客户端?
答案
架构
消息模型
struct Message: Codable {
let messageId: String
let chatId: String
let senderId: String
let content: MessageContent
let timestamp: TimeInterval
var status: MessageStatus // sending / sent / delivered / read / failed
}
enum MessageContent: Codable {
case text(String)
case image(url: String, width: Int, height: Int)
case voice(url: String, duration: Int)
}
WebSocket 连接管理
class IMConnection {
private var webSocket: URLSessionWebSocketTask?
private var heartbeatTimer: Timer?
func connect() {
let session = URLSession(configuration: .default)
webSocket = session.webSocketTask(with: URL(string: "wss://im.example.com")!)
webSocket?.resume()
startHeartbeat()
receiveMessage()
}
private func startHeartbeat() {
heartbeatTimer = Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { [weak self] _ in
self?.send(type: "ping")
}
}
// 自动重连(指数退避)
private var retryCount = 0
func reconnect() {
let delay = min(pow(2.0, Double(retryCount)), 60)
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
self?.retryCount += 1
self?.connect()
}
}
}
关键设计点
| 设计点 | 方案 |
|---|---|
| 消息有序 | 服务端分配递增序列号 |
| 消息 ACK | 发送→服务端ACK→对端ACK |
| 离线消息 | 上线后拉取未读消息 |
| 已读回执 | 批量上报已读位置 |
| 消息缓存 | Core Data + NSFetchedResultsController |
常见面试问题
Q1: 如何保证消息不丢不重?
答案:客户端生成唯一消息 ID(UUID),服务端去重。发送后等待 ACK,超时重发。服务端维护每个会话的消息序列号,客户端拉取时比对序列号补齐缺失消息。