feat: 初始化 iOS 远程控制端项目
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import Foundation
|
||||
|
||||
protocol SignalingClientDelegate: AnyObject {
|
||||
func signalingDidConnect()
|
||||
func signalingDidDisconnect()
|
||||
func signaling(didFail error: String)
|
||||
func signaling(didReceive message: SignalMessage)
|
||||
}
|
||||
|
||||
/// 信令 WebSocket 客户端(URLSessionWebSocketTask 实现)。
|
||||
/// 连接成功后自动发送 REGISTER(deviceType = CONTROLLER),
|
||||
/// 所有回调均已切换到主线程。
|
||||
final class SignalingClient: NSObject {
|
||||
|
||||
weak var delegate: SignalingClientDelegate?
|
||||
|
||||
private let serverUrl: String
|
||||
private let deviceId: String
|
||||
private var session: URLSession?
|
||||
private var task: URLSessionWebSocketTask?
|
||||
private var manuallyClosed = false
|
||||
|
||||
init(serverUrl: String, deviceId: String) {
|
||||
self.serverUrl = serverUrl
|
||||
self.deviceId = deviceId
|
||||
super.init()
|
||||
}
|
||||
|
||||
func connect() {
|
||||
guard let url = URL(string: serverUrl) else {
|
||||
notifyError("无效的服务器地址: \(serverUrl)")
|
||||
return
|
||||
}
|
||||
manuallyClosed = false
|
||||
let config = URLSessionConfiguration.default
|
||||
config.timeoutIntervalForRequest = 15
|
||||
session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
|
||||
task = session?.webSocketTask(with: url)
|
||||
task?.resume()
|
||||
receiveLoop()
|
||||
}
|
||||
|
||||
func disconnect() {
|
||||
manuallyClosed = true
|
||||
task?.cancel(with: .normalClosure, reason: "Disconnecting".data(using: .utf8))
|
||||
task = nil
|
||||
session?.invalidateAndCancel()
|
||||
session = nil
|
||||
}
|
||||
|
||||
func send(_ message: SignalMessage) {
|
||||
guard let task else { return }
|
||||
guard let data = try? JSONEncoder().encode(message),
|
||||
let json = String(data: data, encoding: .utf8) else { return }
|
||||
task.send(.string(json)) { error in
|
||||
if let error {
|
||||
NSLog("[Signaling] send error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isConnected: Bool { task != nil }
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func registerDevice() {
|
||||
var msg = SignalMessage()
|
||||
msg.type = "REGISTER"
|
||||
msg.fromDeviceId = deviceId
|
||||
msg.deviceType = "CONTROLLER"
|
||||
send(msg)
|
||||
}
|
||||
|
||||
private func receiveLoop() {
|
||||
task?.receive { [weak self] result in
|
||||
guard let self else { return }
|
||||
switch result {
|
||||
case .success(let wsMessage):
|
||||
if case .string(let text) = wsMessage,
|
||||
let data = text.data(using: .utf8),
|
||||
let message = try? JSONDecoder().decode(SignalMessage.self, from: data) {
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.signaling(didReceive: message)
|
||||
}
|
||||
}
|
||||
self.receiveLoop()
|
||||
case .failure(let error):
|
||||
if !self.manuallyClosed {
|
||||
self.notifyError(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func notifyError(_ text: String) {
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.signaling(didFail: text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - URLSessionWebSocketDelegate
|
||||
|
||||
extension SignalingClient: URLSessionWebSocketDelegate {
|
||||
|
||||
func urlSession(_ session: URLSession,
|
||||
webSocketTask: URLSessionWebSocketTask,
|
||||
didOpenWithProtocol protocol: String?) {
|
||||
registerDevice()
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.signalingDidConnect()
|
||||
}
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession,
|
||||
webSocketTask: URLSessionWebSocketTask,
|
||||
didCloseWith closeCode: URLSessionWebSocketTask.CloseCode,
|
||||
reason: Data?) {
|
||||
guard !manuallyClosed else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.signalingDidDisconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user