You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
        }
    }
}

注意事项

  1. 必须在主线程更新UI:你已经用DispatchQueue.main.async包裹了UI操作,这一点非常重要,因为Socket.io的回调默认在后台线程执行。
  2. 避免内存泄漏:使用闭包时一定要添加[weak self],代理模式要用弱引用数组。
  3. 及时移除监听/代理:在ViewController销毁或隐藏时,记得移除对应的监听或代理,避免无效回调和内存泄漏。

内容的提问来源于stack exchange,提问作者Sarmad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 22:48:00