Removing a Closure from an Array

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



Leave a reply



Submit