Holding and releasing Closures in an array
So I come up with this solution:
class Wrapper<V> {
var observer: (V) -> Void
public init(_ b: @escaping (V) -> Void) {
observer = b
}
}
class Observable<V> {
public var value: V { didSet {
let enumerator = observers.objectEnumerator()
while let wrapper = enumerator?.nextObject() {
(wrapper as! Wrapper<V>).observer(value)
}
}}
private var observers = NSMapTable<AnyObject, Wrapper<V>>(keyOptions: [.weakMemory], valueOptions: [.strongMemory])
public init(_ initital: V) {
value = initital
}
public func observe(_ subscriber: AnyObject, with closure: @escaping (V) -> Void) {
let wrapper = Wrapper(closure)
observers.setObject(wrapper, forKey: subscriber)
}
}
The final API require subscriber to identify themselves at calling:
Observable.observe(self /* <-- extra param */) { /* closure */ }
Although we cannot weak ref a closure, but with NSMapTable
, we can weak ref the subscriber
object, then use it as a weak key to track observer closure. This allow deallocation of subscriber
thus automatically cleanup outdated observers.
Finally, here's the code for a demo. Expand the snippet and copy-paste to swift playground and see it live.
import Foundation
func setTimeout(_ delay: TimeInterval, block:@escaping ()->Void) -> Timer { return Timer.scheduledTimer(timeInterval: delay, target: BlockOperation(block: block), selector: #selector(Operation.main), userInfo: nil, repeats: false)}
class Wrapper<V> { var observer: (V) -> Void public init(_ b: @escaping (V) -> Void) { observer = b }}
class Observable<V> { public var value: V { didSet { let enumerator = observers.objectEnumerator() while let wrapper = enumerator?.nextObject() { (wrapper as! Wrapper<V>).observer(value) } }} private var observers = NSMapTable<AnyObject, Wrapper<V>>(keyOptions: [.weakMemory], valueOptions: [.strongMemory])
public init(_ initital: V) { value = initital } public func observe(_ subscriber: AnyObject, with closure: @escaping (V) -> Void) { let wrapper = Wrapper(closure) observers.setObject(wrapper, forKey: subscriber) }}
class Consumer { private var id: String
public init(_ id: String, _ observable: Observable<Int>) { self.id = id observable.observe(self) { val in print("[\(id)]", "ok, i see value changed to", val) } } deinit { print("[\(id)]", "I'm out") }}
func demo() -> Any { let observable = Observable(1) var list = [AnyObject]()
list.append(Consumer("Alice", observable)) list.append(Consumer("Bob", observable)) observable.value += 1
// pop Bob, so he goes deinit list.popLast() // deferred setTimeout(1.0) { observable.value += 1 observable.value += 1 }
return [observable, list]}
// need to hold ref to see the effectlet refHolder = demo()
Remove listener that uses a closure from array
I have something similar in my code, I wrote something like this:
func removeListener(listener: NetworkStatusListener) {
listeners = listeners.filter { $0 as AnyObject !== listener as AnyObject }
}
That check will do a reference check, so as long as you have the reference of the listener to remove this should work fine. If not you can change the filter closure to use an id, or some other distinguishing factor.
Finding and removing a DOM Element kept within a closure on a jQuery plugin instance
JQuery provides an .is
function (https://api.jquery.com/is/) to test if an element is the same.
function _generate_closures (default_color, inst) { var group = []; var color = default_color; return { update : function (style) { color = (style) ? style : default_color; }, apply : function (style) { style = (style) ? style : color; for (var i = 0; i < group.length; i++) { group[i].css('color', style); } }, add : function ($elem) { group.push($elem); alert('Group length is ' + group.length); }, remove : function ($elem) { for (var i = 0; i < group.length; i++) { if (group[i].is($elem)) { group.splice(i,1); } alert('Group length is ' + group.length); } }, unbind_events : function (selector) { // unbind using namespaced events } };}
var obj = _generate_closures('orange');var $el= $('li:nth-child(2)');obj.add($el);obj.apply();obj.remove($el);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><ul> <li>line 1</li> <li>line 2</li> <li>line 3</li> <li>line 4</li></ul>
Safe way to remove items from an array while iterating?
First, I assume that array
here is a property (otherwise the question doesn't make a lot of sense). Second, I assume that array
is already made thread-safe. Maybe something like this, which provides synchronized access at the array level.
class Container {
let arrayQueue = DispatchQueue(label: "arrayQueue", attributes: .concurrent)
var _array: [() -> Void] = []
var array: [() -> Void] {
get {
return arrayQueue.sync { return _array }
}
set {
arrayQueue.async(flags: .barrier) {
self._array = newValue
}
}
}
}
With that kind of system, you can make an atomic executor that clears the array:
func execute() {
arrayQueue.async(flags: .barrier) {
// Make a local copy of the array
let toExecute = self._array
// Clear the array (we have a barrier, so there's no race condition)
self._array.removeAll()
// async to another queue to avoid blocking access while executing
DispatchQueue.global().async {
for closure in toExecute {
closure()
}
}
}
}
(You mentioned "without using thread locks." You should almost never be using thread locks in Swift. That's what GCD is for, and it does a lot of work to avoid locking so you don't have to. If your goal were to do this without using GCD, then no, there is no way to safely mutate a property from multiple threads without a concurrency system of some sort, and the best concurrency system for Swift is GCD.)
Adding Swift closure object to NSMutableArray and then removing it doesn't work - LEAK
To re-emphasize, our codebase uses Swift that interops with ObjC, therefore, in my case its not possible to go pure Swift trivially.
I changed our API to use an NSDictionary that maps from NSUInteger handle to the closure and that integer is then used for removal of the closure from the dictionary.
The purpose of that API is to register listener callbacks and have a facility to unregister them. So that NSUInteger handle satisfies the removal bit.
removing an appended element using javascript/closure?
The function is this:
goog.dom.removeNode = function(node) {
return node && node.parentNode ? node.parentNode.removeChild(node) : null;
};
So the code is like below(assume the search box is the parent of the close button):
goog.events.listen(closeButton, goog.events.EventType.CLICK, function() {
goog.dom.removeNode(this.parentNode);
}, false, this);
Swift - How to properly remove blocks from an array when a caller is deallocated?
Your singleton design has some problems.
Having updateBlock
be a variable who's didSet method appends a block to your updateBlocks
array is bad design.
I would suggest getting rid of the updateBlock var, and instead defining an addUpdateBlock
method and a removeAllUpdateBlocks
method:
func addUpdateBlock(_ block () -> ()) {
updateBlocks.append(block)
}
func removeAllUpdateBlocks() {
updateBlocks.removeAll()
}
func executeUpdateBlocks() {
for block in updateBlocks {
block()
}
}
If you want to remove single blocks then you'll need some way to keep track of them. As rmaddy says, you would need some sort of ID for each block. You could refactor your container for your blocks to be a dictionary and use sequential integer keys. When you add a new block, your addBlock function could return the key:
var updateBlocks = [Int: () -> ()]()
var nextBlockID: Int = 0
func addUpdateBlock(_ block () -> ()) -> Int {
updateBlocks[nextBlockID] = block
let result = nextBlockID
nextBlockID += 1
//Return the block ID of the newly added block
return result
}
func removeAllUpdateBlocks() {
updateBlocks.removeAll()
}
func removeBlock(id: Int) -> Bool {
if updateBlocks[id] == nil {
return false
} else {
updateBlocks[id] = nil
return true
}
func executeUpdateBlocks() {
for (_, block) in updateBlocks {
block()
}
If you save your blocks in a dictionary then they won't be executed in any defined order.
Related Topics
How to Get a Double Value Up to 2 Decimal Places
Problems Accessing Calendar Using Ekeventstore on Osx Sierra with Swift 3
Swiftlint Overriding Project Settings Related to Spm
Passing and Storing Closures/Callbacks in Swift
Swift/Scenekit Problems Getting Touch Events from Scnscene and Overlayskscene
In Swift, Can You Find All Types in a Module That Adhere to a Specific Protocol
How to Implement a Swift Protocol Across Structs with Conflicting Property Names
Swift 3 Init Method That Accepts JSON with Optional Parameters
How to Prevent Eventstore Access Error on First Run
Custom Intialiser on Primitive Types for JSONdecoder
How to Create Right Angle View in Swift
In What Situation Would One Use Expectationfornotification in Swift Testing
How to Apply a Context Menu to Buttons in a Swiftui List Row
How to Have Arview and Arscnview Coexist
Swift Codable - Parse JSON Array Which Can Contain Different Data Type
Passing Arguments to #Selector Method in Swift
iOS Swift Error: 'T' Is Not Convertible to 'Mirrordisposition'