blob: 461edefacadc1695f1127445b5276d903bdef30c [file] [log] [blame]
// Copyright 2017 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 "ios/shared/chrome/browser/ui/commands/command_dispatcher.h"
#include <objc/runtime.h>
#include <unordered_map>
#include <vector>
#include "base/logging.h"
#include "base/strings/sys_string_conversions.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@implementation CommandDispatcher {
// Stores which target to forward to for a given selector.
std::unordered_map<SEL, __weak id> _forwardingTargets;
}
- (void)startDispatchingToTarget:(id)target forSelector:(SEL)selector {
DCHECK(_forwardingTargets.find(selector) == _forwardingTargets.end());
_forwardingTargets[selector] = target;
}
- (void)startDispatchingToTarget:(id)target forProtocol:(Protocol*)protocol {
unsigned int methodCount;
objc_method_description* requiredInstanceMethods =
protocol_copyMethodDescriptionList(protocol, YES /* isRequiredMethod */,
YES /* isInstanceMethod */,
&methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
[self startDispatchingToTarget:target
forSelector:requiredInstanceMethods[i].name];
}
}
- (void)stopDispatchingForSelector:(SEL)selector {
_forwardingTargets.erase(selector);
}
- (void)stopDispatchingForProtocol:(Protocol*)protocol {
unsigned int methodCount;
objc_method_description* requiredInstanceMethods =
protocol_copyMethodDescriptionList(protocol, YES /* isRequiredMethod */,
YES /* isInstanceMethod */,
&methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
[self stopDispatchingForSelector:requiredInstanceMethods[i].name];
}
}
// |-stopDispatchingToTarget| should be called much less often than
// |-forwardingTargetForSelector|, so removal is intentionally O(n) in order
// to prioritize the speed of lookups.
- (void)stopDispatchingToTarget:(id)target {
std::vector<SEL> selectorsToErase;
for (auto& kv : _forwardingTargets) {
if (kv.second == target) {
selectorsToErase.push_back(kv.first);
}
}
for (auto* selector : selectorsToErase) {
[self stopDispatchingForSelector:selector];
}
}
#pragma mark - NSObject
// Overridden to forward messages to registered handlers.
- (id)forwardingTargetForSelector:(SEL)selector {
auto target = _forwardingTargets.find(selector);
if (target != _forwardingTargets.end()) {
return target->second;
}
return [super forwardingTargetForSelector:selector];
}
@end