views:

64

answers:

3

Hey, right now I have implemented my own listener pattern. I will send an update to the listeners using fast enumeration. the code will look like this

- (void) updateListeners {
for (id<AProtocol>listener in _listeners)
{
   [listener update];
}

and in listener, i implement method for AProtocol, which is update. suppose there are n object in _listeners, and m number of listener such that m < n want to remove it self from listen when listener's update method is called. The problem with this is that I can't remove when the fast enumeration is ongoing, I will get an error. In order to make the listener more dynamic so that we can remove listener from _listeners when update method is called, what would be the solution?( I don't want to use NSNotificationCenter)

+4  A: 

It sounds like what you have now is the listener itself deciding whether it should be removed, and removing itself. That's problematic because (a) as you say, it breaks your enumeration, but (b) because it's a tricky abstraction-- if the object that runs "update" doesn't also control ownership in the listener list directly, your design pattern might run into problems anyways. I might suggest that you redefine update listeners like this:

- (BOOL)update

and return a BOOL indicating whether the listener should be removed (or kept, depending on your semantics). Then you could write the loop like this:

NSMutableSet * listenersToBeRemoved = [NSMutableSet set];
for (id<AProtocol> listener in _listeners) {
    BOOL shouldRemove = [listener update];
    if (shouldRemove) { 
        [listenersToBeRemoved addObject:listener];
    }
}
// Do this if _listeners is a Set, or whatever the equivalent is.
[_listeners minusSet:listenersToBeRemoved];

As others have suggested, if you do want to allow the listeners to remove themselves during the update process, it's simple enough to just iterate through a local copy of the collection, instead of the collection itself. The syntax for that depends on whether _listeners is an array, a set, or something else, but see other answers or the docs.

quixoto
this one works pretty good. It is safer to remove the listen after the emulation
David Gao
A: 

Replace fast iteration by usual iteration and start from the last.

// must iterate from the last in case the current listener removes itself from the list
for (int i = [_listeners count] - 1; i > -1; i--) {
    id<AProtocol> listener = [_listeners objectAtIndex:i];
    [listener update];
}
David
What if `_listeners` isn't an array? (Set, or unlikely, a custom collection.)
quixoto
Well, I don't know. Actually, I won't stand for my answer since I think Ortwin Gentz has given the best answer. My answer is a bit of a hack and yours does not seem natural to me.
David
+2  A: 

Why not operate the enumeration on a copy of the array?

for (id<AProtocol>listener in [NSArray arrayWithArray:_listeners])
{
   [listener update];
}

Then _listeners can safely be modified during the loop. It's safer than Davids solution since it's immune against any listener removals not only the ones that happen in -update.

Ortwin Gentz
Why do people always do `[NSArray arrayWithArray:_listeners]` instead of just `[_listeners copy]` ?
JeremyP
@JeremyP: My 2 cents: I tend to do the `arrayWithArray` and `setWithSet` because I generally find it more readable, in the sense that it's very clear that that's a shallow copy. You also can omit the memory management if you're just using it in a throwaway context like this, unlike `copy`.
quixoto
Good point about the memory management. Thanks.
JeremyP