/** EOObserver.m
This is the central coordinating class of the change tracking system * of EOControl. It manages the observers [EOObserving] and the objects * to be observed. No instances of this class should be needed or created. * All methods for coordinating the tracking mechanism are class methods.
*Observers must implement the [EObserving] protocol, in particular * [EOObserving-objectWillChange:] while the observed objects must invoke * [NSObject-willChange]. Invoke [+addObserver:forObject:] to register an * observer for a particular object. That will setup for * [EOObserving-objectWillChange:] to be invoked on the observer each time * [NSObject-willChange] gets called. To remove an observer invoke * [+removeObserver:forObject:]. For observers who wish to be notified for * every change the methods [+addOmniscientObserver:] and * [+removeOmniscientObserver:] can be used.
*/ @implementation EOObserverCenter static NSMapTable *observersMap = NULL; static GDL2NonRetainingMutableArray *omniscientObservers = NULL; static unsigned int notificationSuppressCount=0; static id lastObject; + (void)initialize { observersMap = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 32); omniscientObservers = [[GDL2NonRetainingMutableArray alloc] initWithCapacity:32]; lastObject = nil; notificationSuppressCount = 0; } /** * Adds the observer to be notified with a [EOObserving-objectWillChange:] * on the first of consecutive [NSObject-willChange] methods invoked on * the object. This does not retain the object. It is the observers * responsibility to unregister the object with [-removeObserver:forObject:] * before the object ceases to exist. * The observer is also not retained. It is the observers responsibility * to unregister before it ceases to exist. */ + (void)addObserver: (idRegisters the observer with the receiver so that it will * dispatch [EODelayedObserver-subjectChanged] either immediately, * if the observers priority is EOObserverPriorityImmediate, or * for the next invocation of [-notifyObserversUpToPriority:]. If * this method is invoked during the processing of * [-notifyObserversUpToPriority:], then the dispatch will be enqueue * for the current processing.
*Upon the first invocation within a run loop, this method registers * a callback method to have [-notifyObserversUpToPriority:] invoked * with EOObserverPrioritySixth with the [NSRunLoop-currentRunLoop] with * EOFlushDelayedObserverRunLoopOrdering and the receivers run loop modes.
*The observer is not retained and should be removed from the receiver * with [-dequeueObserver:] during the EODelayedObservers [NSObject-dealloc] * method.
*/ - (void)enqueueObserver: (EODelayedObserver *)observer { EOObserverPriority priority = [observer priority]; if (priority == EOObserverPriorityImmediate) [observer subjectChanged]; else { if (_queue[priority]) { EODelayedObserver *obj = _queue[priority]; EODelayedObserver *last = nil; for (; obj != nil && obj != observer; obj = obj->_next) { last = obj; } if (obj == observer) { return; } NSAssert2(observer->_next == nil, @"observer:%@ has ->next:%@", observer, observer->_next); NSAssert(last != nil, @"Currupted Queue"); last->_next = observer; } else _queue[priority] = observer; if (priority > _highestNonEmptyQueue) { _highestNonEmptyQueue = priority; } if (_haveEntryInNotificationQueue == NO) { [[NSRunLoop currentRunLoop] performSelector: @selector(_notifyObservers:) target: self argument: nil order: EOFlushDelayedObserversRunLoopOrdering modes: _modes]; _haveEntryInNotificationQueue = YES; } } } /** * Unregisters the observer from the receiver. */ - (void)dequeueObserver: (EODelayedObserver *)observer { EOObserverPriority priority; EODelayedObserver *obj, *last = nil; if (!observer) return; priority = [observer priority]; obj = _queue[priority]; while (obj) { if (obj == observer) { if (last) { last->_next = obj->_next; obj->_next = nil; } else { _queue[priority] = obj->_next; obj->_next = nil; } if (!_queue[priority]) { int i = priority; if (priority >= _highestNonEmptyQueue) { for (; i > EOObserverPriorityImmediate; --i) { if (_queue[i]) { _highestNonEmptyQueue = i; break; } } } if (priority == EOObserverPriorityFirst || i == EOObserverPriorityImmediate) { _highestNonEmptyQueue = EOObserverPriorityImmediate; } } return; } last = obj; obj = obj->_next; } } /** *Dispatches registered observer notifications by sending * [EODelayedObserver-subjectChanged] to all registered observers * of the receiver. During the processing new enqueObserver: methods will * be honored. It is up toe the callers to ensure no loops result.
*This method is invoked automatically with EOObserverPrioritySixth * during each run loop after an invocation of [-enqueueObserver:].
*Note that unlike the reference implementation, we dequeue the * observer after dispatching [EODelayedObserver-subjectChanged].
*/ - (void)notifyObserversUpToPriority: (EOObserverPriority)priority { EOObserverPriority i = EOObserverPriorityFirst; EODelayedObserver *observer = nil; while (i <= priority) { observer = _queue[i]; if (observer) { [self dequeueObserver: observer]; [observer subjectChanged]; i = EOObserverPriorityFirst; } else { i++; } } } /** * Sets the run loop modes the receiver uses when registering * for [-notifyObserversUpToPriority:] during [-enqueueObserver:]. * (see [NSRunLoop]). */ - (void)setRunLoopModes: (NSArray *)modes { ASSIGN(_modes, modes); } /** * Returns the run loop modes the receiver uses when registering * for [-notifyObserversUpToPriority:] during [-enqueueObserver:]. * (see [NSRunLoop]). */ - (NSArray *)runLoopModes { return _modes; } @end /** * This is a convenience class which is initialized with a target, * an action method and a priority, to have the action method * invoked on the target when the subjectChanged method is invoked * by the EODelayedObserverQueue. */ @implementation EOObserverProxy /** * Initializes the receiver to dispatch action to target upon processing * [-subjectChanged]. The receiver uses priority for the * [EODelayedObserverQueue]. * Target is not retained. */ - (id)initWithTarget: (id)target action: (SEL)action priority: (EOObserverPriority)priority { if ((self = [super init])) { _target = target; _action = action; _priority = priority; } return self; } - (void) dealloc { [self discardPendingNotification]; [super dealloc]; } /** * Returns the priority the receiver was initialized with. */ - (EOObserverPriority)priority { return _priority; } /** * Overridden to dispatch the action method to the target which * the receiver was initialized with. */ - (void)subjectChanged { [_target performSelector: _action withObject: self]; } @end