What you need is a kind of weak array. As far as I know, there is no such type in the Swift foundation classes: My first thought was to use NSMapTable
or something similar with weakToStrongObjects
configuration, which is documented to store the keys as weak references, but unfortunately that class does not reorganize itself automatically, see the discussion part:
Use of weak-to-strong map tables is not recommended. The strong values for weak keys > which get zeroed out continue to be maintained until the map table resizes itself.
What you could do is:
- Create a boxing object that stores a weak reference and the closure
- Add that box into the
callBacks
array.
- When calling
callAll
, first clean up the callBacks
array to remove all boxes with nil
references.
That manual cleanup is a drawback, because you will not be informed when a weak reference is nil
-led by ARC. But it is done centrally by the Container, so the clients do not need to take care.
For example:
typealias CallbackType = () -> Void
class WeakCallback : Equatable {
let id = UUID()
weak var refItem: AnyObject?
let callback:CallbackType
init(refItem: AnyObject, callback:@escaping CallbackType) {
self.refItem = refItem
self.callback = callback
}
static func == (lhs: WeakCallback, rhs: WeakCallback) -> Bool {
return lhs.id == rhs.id
}
}
class CallbackContainer {
var callBacks = [WeakCallback]()
func add(ref:AnyObject, callback: @escaping () -> Void) -> WeakCallback {
cleanupCallbackArray()
let cl = WeakCallback(refItem:ref, callback: callback)
callBacks.append(cl)
return cl
}
func callAll() {
cleanupCallbackArray()
for cb in callBacks {
if (cb.refItem != nil) {
cb.callback()
} else {
print ("oops, found nil for (cb.id)")
}
}
}
func cleanupCallbackArray() {
callBacks = callBacks.filter { cb -> Bool in
if (cb.refItem == nil) {
print ("(cb.id) has nil reference, will be removed")
return false
}
return true
}
}
}
And then, in viewDidLoad
, add the view controller as a reference to the container:
override func viewDidLoad() {
super.viewDidLoad()
callback = container.add (ref:self) {
debugPrint("Called callback on A")
}
}
To test:
let cbContainer = CallbackContainer()
var aVC: AViewController? = AViewController(container: cbContainer)
aVC?.viewDidLoad()
print ("callAll 1")
cbContainer.callAll()
print ("set to nil")
aVC = nil
print ("callAll 2")
cbContainer.callAll()
gives:
callAll 1
"Called callback on A"
set to nil
"Deinited AViewController"
callAll 2
81C3D375-4B0E-4806-8088-AFE81C3125F3 has nil reference, will be removed
As you can see, the closure will not be called upon "callAll 2".
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…