|  | // Copyright 2014 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #import "base/ios/crb_protocol_observers.h" | 
|  |  | 
|  | #include <objc/runtime.h> | 
|  | #include <stddef.h> | 
|  | #include <algorithm> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/logging.h" | 
|  | #include "base/mac/scoped_nsobject.h" | 
|  | #include "base/stl_util.h" | 
|  |  | 
|  | @interface CRBProtocolObservers () { | 
|  | base::scoped_nsobject<Protocol> _protocol; | 
|  | // ivars declared here are private to the implementation but must be | 
|  | // public for allowing the C++ |Iterator| class access to those ivars. | 
|  | @public | 
|  | // vector of weak pointers to observers. | 
|  | std::vector<__unsafe_unretained id> _observers; | 
|  | // The nested level of observer iteration. | 
|  | // A depth of 0 means nobody is currently iterating on the list of observers. | 
|  | int _invocationDepth; | 
|  | } | 
|  |  | 
|  | // Removes nil observers from the list and is called when the | 
|  | // |_invocationDepth| reaches 0. | 
|  | - (void)compact; | 
|  |  | 
|  | @end | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | class Iterator { | 
|  | public: | 
|  | explicit Iterator(CRBProtocolObservers* protocol_observers); | 
|  | ~Iterator(); | 
|  | id GetNext(); | 
|  |  | 
|  | private: | 
|  | CRBProtocolObservers* protocol_observers_; | 
|  | size_t index_; | 
|  | size_t max_index_; | 
|  | }; | 
|  |  | 
|  | Iterator::Iterator(CRBProtocolObservers* protocol_observers) | 
|  | : protocol_observers_(protocol_observers), | 
|  | index_(0), | 
|  | max_index_(protocol_observers->_observers.size()) { | 
|  | DCHECK(protocol_observers_); | 
|  | ++protocol_observers->_invocationDepth; | 
|  | } | 
|  |  | 
|  | Iterator::~Iterator() { | 
|  | if (protocol_observers_ && --protocol_observers_->_invocationDepth == 0) | 
|  | [protocol_observers_ compact]; | 
|  | } | 
|  |  | 
|  | id Iterator::GetNext() { | 
|  | if (!protocol_observers_) | 
|  | return nil; | 
|  | auto& observers = protocol_observers_->_observers; | 
|  | // Skip nil elements. | 
|  | size_t max_index = std::min(max_index_, observers.size()); | 
|  | while (index_ < max_index && !observers[index_]) | 
|  | ++index_; | 
|  | return index_ < max_index ? observers[index_++] : nil; | 
|  | } | 
|  | } | 
|  |  | 
|  | @interface CRBProtocolObservers () | 
|  |  | 
|  | // Designated initializer. | 
|  | - (id)initWithProtocol:(Protocol*)protocol; | 
|  |  | 
|  | @end | 
|  |  | 
|  | @implementation CRBProtocolObservers | 
|  |  | 
|  | + (instancetype)observersWithProtocol:(Protocol*)protocol { | 
|  | return [[[self alloc] initWithProtocol:protocol] autorelease]; | 
|  | } | 
|  |  | 
|  | - (id)init { | 
|  | NOTREACHED(); | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | - (id)initWithProtocol:(Protocol*)protocol { | 
|  | self = [super init]; | 
|  | if (self) { | 
|  | _protocol.reset([protocol retain]); | 
|  | } | 
|  | return self; | 
|  | } | 
|  |  | 
|  | - (Protocol*)protocol { | 
|  | return _protocol.get(); | 
|  | } | 
|  |  | 
|  | - (void)addObserver:(id)observer { | 
|  | DCHECK(observer); | 
|  | DCHECK([observer conformsToProtocol:self.protocol]); | 
|  |  | 
|  | if (base::ContainsValue(_observers, observer)) | 
|  | return; | 
|  |  | 
|  | _observers.push_back(observer); | 
|  | } | 
|  |  | 
|  | - (void)removeObserver:(id)observer { | 
|  | DCHECK(observer); | 
|  | auto it = std::find(_observers.begin(), _observers.end(), observer); | 
|  | if (it != _observers.end()) { | 
|  | if (_invocationDepth) | 
|  | *it = nil; | 
|  | else | 
|  | _observers.erase(it); | 
|  | } | 
|  | } | 
|  |  | 
|  | - (BOOL)empty { | 
|  | int count = 0; | 
|  | for (id observer : _observers) { | 
|  | if (observer != nil) | 
|  | ++count; | 
|  | } | 
|  | return count == 0; | 
|  | } | 
|  |  | 
|  | #pragma mark - NSObject | 
|  |  | 
|  | - (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { | 
|  | NSMethodSignature* signature = [super methodSignatureForSelector:selector]; | 
|  | if (signature) | 
|  | return signature; | 
|  |  | 
|  | // Look for a required method in the protocol. protocol_getMethodDescription | 
|  | // returns a struct whose fields are null if a method for the selector was | 
|  | // not found. | 
|  | struct objc_method_description description = | 
|  | protocol_getMethodDescription(self.protocol, selector, YES, YES); | 
|  | if (description.types) | 
|  | return [NSMethodSignature signatureWithObjCTypes:description.types]; | 
|  |  | 
|  | // Look for an optional method in the protocol. | 
|  | description = protocol_getMethodDescription(self.protocol, selector, NO, YES); | 
|  | if (description.types) | 
|  | return [NSMethodSignature signatureWithObjCTypes:description.types]; | 
|  |  | 
|  | // There is neither a required nor optional method with this selector in the | 
|  | // protocol, so invoke -[NSObject doesNotRecognizeSelector:] to raise | 
|  | // NSInvalidArgumentException. | 
|  | [self doesNotRecognizeSelector:selector]; | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | - (void)forwardInvocation:(NSInvocation*)invocation { | 
|  | DCHECK(invocation); | 
|  | if (_observers.empty()) | 
|  | return; | 
|  | SEL selector = [invocation selector]; | 
|  | Iterator it(self); | 
|  | id observer; | 
|  | while ((observer = it.GetNext()) != nil) { | 
|  | if ([observer respondsToSelector:selector]) | 
|  | [invocation invokeWithTarget:observer]; | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void)executeOnObservers:(ExecutionWithObserverBlock)callback { | 
|  | DCHECK(callback); | 
|  | if (_observers.empty()) | 
|  | return; | 
|  | Iterator it(self); | 
|  | id observer; | 
|  | while ((observer = it.GetNext()) != nil) | 
|  | callback(observer); | 
|  | } | 
|  |  | 
|  | #pragma mark - Private | 
|  |  | 
|  | - (void)compact { | 
|  | DCHECK(!_invocationDepth); | 
|  | _observers.erase(std::remove(_observers.begin(), _observers.end(), nil), | 
|  | _observers.end()); | 
|  | } | 
|  |  | 
|  | @end |