Socket.io单例类下多ViewController接收数据后执行UI操作问题
解决不同ViewController中Socket.io接收数据后执行UI操作的问题
你当前遇到的核心问题是全局代理变量只能绑定一个ViewController,当你切换页面后,如果没有及时更新代理对象,就会导致回调失效。下面给你几个实用的解决方案,你可以根据自己的需求选择:
方案一:使用NotificationCenter(通知中心)
这是最简单的实现方式,不需要维护代理关系,多个ViewController可以同时监听Socket数据更新。
步骤1:修改SocketHelper的监听逻辑
在收到rideFares事件时,通过通知中心发送数据:
enum Events { case search func getFareEstimate(params: NSDictionary){ SocketHelper.shared.socket.emit("fareEstimate" , params) } func listen() { SocketHelper.shared.socket!.on("rideFares") { (response, emitter) in print(response) guard let dic = response as? NSArray, let data = dic[0] as? NSDictionary, let fare = data["fare"] as? NSNumber else { return } DispatchQueue.main.async { // 发送自定义通知,携带fare数据 NotificationCenter.default.post( name: NSNotification.Name("FareUpdatedNotification"), object: nil, userInfo: ["fare": fare] ) } } } }
步骤2:在ViewController中监听通知
每个需要处理UI更新的ViewController都可以注册通知,并在销毁时移除监听:
class YourViewController: UIViewController { @IBOutlet weak var fareLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() // 注册通知监听 NotificationCenter.default.addObserver( self, selector: #selector(handleFareUpdate(_:)), name: NSNotification.Name("FareUpdatedNotification"), object: nil ) } @objc private func handleFareUpdate(_ notification: Notification) { if let fare = notification.userInfo?["fare"] as? NSNumber { // 执行UI操作,比如更新标签文本 fareLabel.text = "当前预估费用:\(fare)" } } deinit { // 移除通知监听,避免内存泄漏 NotificationCenter.default.removeObserver(self) } }
方案二:多代理模式(支持多个ViewController同时接收回调)
如果你更倾向于使用代理模式,可以改成多代理数组,让多个ViewController同时成为代理,只要VC没销毁就能收到回调。
步骤1:修改SocketHelper的代理逻辑
首先把协议改成类类型,并用弱引用数组存储代理:
// 协议添加AnyObject,确保只能被类实现(支持弱引用) protocol TestUI: AnyObject { func fareUpdate(amount: NSNumber) } class SocketHelper { static let shared = SocketHelper() var socket: SocketIOClient! let manager = SocketManager(socketURL: URL(string: "http://13.59.81.136:3355/")!, config: [.log(true), .compress, .connectParams(["token":user.shared.token])] ) // 用弱引用数组存储多个代理,避免内存泄漏 private var delegates = [Weak<TestUI>]() private init() { socket = manager.defaultSocket } // 添加代理 func addDelegate(_ delegate: TestUI) { if !delegates.contains(where: { $0.value === delegate }) { delegates.append(Weak(value: delegate)) } } // 移除代理 func removeDelegate(_ delegate: TestUI) { delegates.removeAll(where: { $0.value === delegate }) } // 弱引用包装类,用于存储代理 private class Weak<T: AnyObject> { weak var value: T? init(value: T) { self.value = value } } // ... 其他原有方法不变 enum Events { case search func getFareEstimate(params: NSDictionary){ SocketHelper.shared.socket.emit("fareEstimate" , params) } func listen() { SocketHelper.shared.socket!.on("rideFares") { (response, emitter) in print(response) guard let dic = response as? NSArray, let data = dic[0] as? NSDictionary, let fare = data["fare"] as? NSNumber else { return } DispatchQueue.main.async { // 遍历所有有效代理,调用回调方法 SocketHelper.shared.delegates.forEach { delegate in delegate.value?.fareUpdate(amount: fare) } } } } } }
步骤2:在ViewController中实现代理
在VC显示时添加代理,隐藏时移除代理:
class YourViewController: UIViewController, TestUI { @IBOutlet weak var fareLabel: UILabel! override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // 页面显示时添加代理 SocketHelper.shared.addDelegate(self) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // 页面隐藏时移除代理 SocketHelper.shared.removeDelegate(self) } func fareUpdate(amount: NSNumber) { // 执行UI操作 fareLabel.text = "当前预估费用:\(amount)" } }
方案三:闭包回调(动态绑定,灵活可控)
这种方式允许每个ViewController注册自己的回调闭包,并且可以随时移除,适合更灵活的场景。
步骤1:修改SocketHelper的闭包管理逻辑
class SocketHelper { static let shared = SocketHelper() var socket: SocketIOClient! let manager = SocketManager(socketURL: URL(string: "http://13.59.81.136:3355/")!, config: [.log(true), .compress, .connectParams(["token":user.shared.token])] ) private init() { socket = manager.defaultSocket } // ... 其他原有方法不变 enum Events { case search func getFareEstimate(params: NSDictionary){ SocketHelper.shared.socket.emit("fareEstimate" , params) } // 用UUID作为key管理闭包,方便移除 private var fareListeners = [UUID: (NSNumber) -> Void]() func listen(with closure: @escaping (NSNumber) -> Void) -> UUID { let listenerId = UUID() fareListeners[listenerId] = closure // 只在第一个闭包注册时监听socket事件 if fareListeners.count == 1 { SocketHelper.shared.socket!.on("rideFares") { (response, emitter) in print(response) guard let dic = response as? NSArray, let data = dic[0] as? NSDictionary, let fare = data["fare"] as? NSNumber else { return } DispatchQueue.main.async { // 调用所有注册的闭包 self.fareListeners.values.forEach { $0(fare) } } } } return listenerId } func stopListening(with listenerId: UUID) { fareListeners.removeValue(forKey: listenerId) // 如果没有监听者了,移除socket事件监听 if fareListeners.isEmpty { SocketHelper.shared.socket.off("rideFares") } } } }
步骤2:在ViewController中注册闭包
class YourViewController: UIViewController { @IBOutlet weak var fareLabel: UILabel! private var fareListenerId: UUID? override func viewDidLoad() { super.viewDidLoad() // 注册回调闭包,注意用[weak self]避免内存泄漏 fareListenerId = SocketHelper.Events.search.listen { [weak self] fare in self?.fareLabel.text = "当前预估费用:\(fare)" } } deinit { // 页面销毁时移除监听 if let id = fareListenerId { SocketHelper.Events.search.stopListening(with: id) } } }
注意事项
- 必须在主线程更新UI:你已经用
DispatchQueue.main.async包裹了UI操作,这一点非常重要,因为Socket.io的回调默认在后台线程执行。 - 避免内存泄漏:使用闭包时一定要添加
[weak self],代理模式要用弱引用数组。 - 及时移除监听/代理:在ViewController销毁或隐藏时,记得移除对应的监听或代理,避免无效回调和内存泄漏。
内容的提问来源于stack exchange,提问作者Sarmad
相关产品推荐
相关产品推荐

