基于Boxing风格的多绑定与销毁实现问题咨询
Box Class Hey there! I totally get the confusion with extending that basic Box class to support multiple bindings and proper cleanup—let's work through this together. The original implementation only handles a single listener, which is why you're running into issues when trying to bind multiple observers. Here's how to fix it:
Step 1: Refactor the Box Class for Multiple Listeners
First, we'll modify the class to store a dictionary of listeners instead of just one. Using unique IDs for each listener makes targeted cleanup straightforward:
class Box<T> { typealias Listener = (T) -> Void // Store listeners with unique UUIDs for easy unbinding private var listeners: [UUID: Listener] = [:] var value: T { didSet { // Notify all registered listeners when the value updates listeners.values.forEach { $0(value) } } } init(_ value: T) { self.value = value } // Bind a listener and return an ID to use for unbinding later func bind(listener: @escaping Listener) -> UUID { let id = UUID() listeners[id] = listener // Immediately send the current value to the new listener listener(value) return id } // Unbind a specific listener using its unique ID func unbind(with id: UUID) { listeners.removeValue(forKey: id) } // Unbind all listeners at once (great for bulk cleanup) func unbindAll() { listeners.removeAll() } }
Step 2: Practical Usage Example
Here's how to use the refactored Box to bind multiple observers and clean them up properly:
// Create a Box instance to track a username let usernameBox = Box("initial_user") // Bind first observer let firstBindingId = usernameBox.bind { newUsername in print("Observer 1: Username updated to \(newUsername)") } // Bind second observer let secondBindingId = usernameBox.bind { newUsername in print("Observer 2: Username changed to \(newUsername)") } // Update the value—both observers will trigger usernameBox.value = "raywenderlich_fan" // Unbind the first observer usernameBox.unbind(with: firstBindingId) // Update again—only the second observer triggers usernameBox.value = "swift_dev" // Clean up all listeners when done (e.g., when a screen is dismissed) usernameBox.unbindAll()
Step 3: Avoid Memory Leaks (Critical!)
If your listeners reference objects like view controllers, you need to use [weak self] to prevent retain cycles. Here's how to implement this in a view controller:
class ProfileViewController: UIViewController { @IBOutlet weak var usernameLabel: UILabel! private var usernameBindingId: UUID? private let userBox = Box("default_user") override func viewDidLoad() { super.viewDidLoad() // Bind with weak self to avoid retaining the VC unnecessarily usernameBindingId = userBox.bind { [weak self] newUsername in guard let self = self else { return } self.usernameLabel.text = newUsername } } // Clean up the binding when the VC is destroyed deinit { if let id = usernameBindingId { userBox.unbind(with: id) } // Alternatively, unbind all if you have multiple bindings // userBox.unbindAll() } }
Key Takeaways
- Replace the single
listenerproperty with a dictionary to support multiple observers - Use UUIDs to track individual bindings for precise, targeted cleanup
- Always use
[weak self]in listener closures when referencing objects that could be destroyed (like view controllers) - Clean up bindings in
deinit(for view controllers) or at the appropriate lifecycle point to avoid memory leaks
内容的提问来源于stack exchange,提问作者Sti

