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

Swift中如何从监听器数组解绑闭包类型监听器(主题引擎场景)

Solution for Unsubscribing Closure Listeners in Your Swift Theme Engine

Hey there! Let's break down how to solve the closure unsubscription problem for your ThemeManager and Box setup. The core issue here is that Swift closures don't natively support equality checks, so we can't just remove a specific closure from an array directly. Instead, we'll wrap each closure in an identifiable object to track and unsubscribe it later.

Step 1: Create a Wrapper Class for Your Theme Listeners

First, we'll define a simple class to wrap our theme update closure along with a unique ID. This ID will let us find and remove the exact listener when needed:

import Foundation

class ThemeListener {
    let uniqueId: UUID
    let updateHandler: (Theme) -> Void
    
    init(handler: @escaping (Theme) -> Void) {
        self.uniqueId = UUID()
        self.updateHandler = handler
    }
}

(Note: Replace Theme with your actual theme type if it's named differently.)

Step 2: Update the Box Class to Use the Wrapper

Modify your Box class to store an array of ThemeListener instances instead of raw closures. We'll add subscribe (which returns the listener for later unsubscription) and unsubscribe methods:

class Box<T> {
    private var listeners: [ThemeListener] = []
    var value: T {
        didSet {
            notifyAllListeners()
        }
    }
    
    init(value: T) {
        self.value = value
    }
    
    // Subscribe to value changes, returns the listener to hold onto for unsubscribing
    func subscribe(to handler: @escaping (Theme) -> Void) -> ThemeListener {
        let listener = ThemeListener(handler: handler)
        listeners.append(listener)
        // Optional: Immediately trigger the handler with the current value
        handler(value as! Theme)
        return listener
    }
    
    // Unsubscribe using the listener instance returned from subscribe()
    func unsubscribe(_ listener: ThemeListener) {
        listeners.removeAll { $0.uniqueId == listener.uniqueId }
    }
    
    private func notifyAllListeners() {
        listeners.forEach { $0.updateHandler(value as! Theme) }
    }
}

(If your Box is generic over your Theme type directly, you can remove the force cast and adjust the handler type to (T) -> Void for cleaner code.)

Step 3: Update BaseViewController to Manage Subscription Lifecycle

In your BaseViewController, store the listener instance and unsubscribe when the view controller is no longer needed (to avoid memory leaks and unnecessary updates):

class BaseViewController: UIViewController {
    private var themeSubscription: ThemeListener?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupThemeSubscription()
    }
    
    private func setupThemeSubscription() {
        themeSubscription = ThemeManager.shared.currentThemeBox.subscribe { [weak self] theme in
            guard let self = self else { return }
            self.applyTheme(theme)
        }
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // Clean up the subscription when the view controller is leaving the screen
        if let listener = themeSubscription {
            ThemeManager.shared.currentThemeBox.unsubscribe(listener)
            themeSubscription = nil
        }
    }
    
    // Subclasses override this to apply theme styles
    func applyTheme(_ theme: Theme) {
        // Default implementation (optional)
    }
}

Key Notes:

  • Avoid Memory Leaks: Always use [weak self] in the closure to prevent a retain cycle between the view controller and the Box/ThemeManager.
  • Unique Identification: Using UUID ensures each listener has a unique identifier, so we never accidentally remove the wrong one.
  • Lifecycle Management: Unsubscribing in viewWillDisappear (or deinit, depending on your needs) ensures we don't keep references to view controllers that are no longer in use.

This approach is clean, maintainable, and solves the core problem of unsubscribing closure-based listeners in Swift.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 07:32:59