许多类使用类似于以下内容的代码来激活侦听器。
typedef enum {
UICollectionElementCategoryCell,
UICollectionElementCategorySupplementaryView,
UICollectionElementCategoryDecorationView
} UICollectionElementCategory;
这一点很好,直到侦听器尝试添加/删除侦听器。从列表内部进行的这种修改会导致- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *attributes = (UICollectionViewLayoutAttributes *)[super layoutAttributesForItemAtIndexPath:indexPath];
if ([attributes representedElementCategory] == UICollectionElementCategoryCell) {
//some good code.
}
return attributes;
}
。我们应该处理这种情况还是应该修改听众无效?处理添加/删除侦听器的最佳方法是什么?
更新
这是一种可能的解决方案。
private List<Listener> listeners = new ArrayList<Listener>();
public void fireListener() {
for(Listener l : listeners) l.someMethod();
}
答案 0 :(得分:15)
有三种情况:
您不希望在侦听器执行期间允许修改侦听器集合:
在这种情况下,ConcurrentModificationException
是合适的。
您希望允许修改侦听器,但更改不会反映在当前运行中:
您必须确保listeners
的修改对当前运行没有影响。 CopyOnWriteArrayList
可以解决问题。在使用它之前阅读API,有一些陷阱
另一个解决方案是在迭代之前复制列表。
您希望listeners
的更改在当前运行中反映出来:
最棘手的案例。使用for-each循环和迭代器在这里不起作用(正如你已经注意到的那样;-))。你必须自己跟踪变化。
一个可能的解决方案是将监听器存储在ArrayList
中并使用标准for循环遍历该列表:
for (int i =0; i < listeners.size(); i++) {
Listener l = listeners.get(i);
if (l == null)
continue;
l.handleEvent();
}
删除侦听器会将元素在数组中的位置设置为null。
新的侦听器被添加到最后,因此将在当前执行中运行
请注意,此解决方案只是一个示例,而不是线程安全!
有时需要一些维护来删除null
元素,以防止列表变得过大。
由你来决定需要什么。
我个人最喜欢的是第二个。它允许在执行时进行修改,但不会改变当前运行的行为,这会导致意外结果。
答案 1 :(得分:9)
我相信在触发时删除侦听器感觉就像代码气味,应该避免使用。
然而,尽管如此,我能尽力回答你的问题:
在我看来,在浏览时添加/删除侦听器的最佳方法是使用支持此的迭代器。
例如:
// Assuming the following:
final List<Listener> listeners = ...
final Iterator<Listener> i = listeners.iterator();
while (i.hasNext()) {
// Must be called before you can call i.remove()
final Listener listener = i.next();
// Fire an event to the listener or whatever...
i.remove(); // that's where the magic happens :)
}
请注意,某些Iterators
不支持Iterator#remove
方法!
答案 2 :(得分:5)
我想说允许听众添加/删除其他听众(或他们自己)并不是一个好主意。它表明关注点分离不佳。毕竟,为什么听众应该对调用者有所了解?你会如何测试这种紧密耦合的系统?
你可以做的事情就是让事件处理方法(在监听器中)返回一个布尔标志,指示监听器不想接收更多事件。这使得事件调度程序负责执行删除操作,它涵盖了大多数需要从侦听器中修改侦听器列表的用例。
这种方法的主要区别在于听众只是简单地说了一些关于自己的事情(即#34;我不想要更多事件和#34;)而不是与调度员的实施联系在一起。这种分离提高了可测试性,并且不会将任何一个类的内部暴露给另一个类。
public interface FooListener {
/**
* @return False if listener doesn't want to receive further events.
*/
public boolean handleEvent(FooEvent event);
}
public class Dispatcher {
private final List<FooListener> listeners;
public void dispatch(FooEvent event) {
Iterator<FooListener> it = listeners.iterator();
while (it.hasNext()) {
if (!it.next().handleEvent(event))
it.remove();
}
}
}
更新:在侦听器中添加和删除其他侦听器稍微有些问题(并且应该引发更响亮的警报铃声)但是您可以遵循类似的模式:侦听器应该返回有关内容的信息需要添加/删除其他侦听器,调度员应对该信息采取行动。
但是在这种情况下,你会遇到很多边缘情况:
所有这些问题都来自于监听器模式本身以及它的基本假设,即列表中的所有监听器将彼此独立。处理这些案件的逻辑肯定应该放在调度员身上而不是听众身上。
更新2:在我的示例中,我使用裸布尔值来简洁,但在实际代码中,我定义了一个双值枚举类型,以使合约更明确。
答案 3 :(得分:2)
您可以先复制listeners
集合,以避免可能的ConcurrentModificationException
:
public void fireListener() {
Listener[] ll = listeners.toArray(new Listener[listeners.size()]);
for(Listener l : ll) l.someMethod();
}
答案 4 :(得分:-1)
您无法添加/删除正在使用的列表/地图。在C#中有一个很好的小类叫做ConcurrentList / ConcurrentDictionary。
在Java中,这是可能的,这里有一个很好的小链接:
https://docs.oracle.com/javase/tutorial/essential/concurrency/collections.html
由于集合在循环中使用,集合将使用一段时间。如果你在循环中调用的函数中添加/删除,你会收到一个错误,因为没有Concurrent就不允许这样做。