blob: ec1f4dd9d8b55d051613cf2dbc8697b2685bd039 [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/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"
#import "ios/chrome/browser/ui/metrics/metrics_recorder.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;
// Stores which MetricsRecorder to notify for a given selector.
std::unordered_map<SEL, __weak id<MetricsRecorder>> _metricsRecorders;
}
- (void)startDispatchingToTarget:(id)target forSelector:(SEL)selector {
DCHECK(![self targetForSelector:selector]);
_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];
}
free(requiredInstanceMethods);
}
- (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];
}
free(requiredInstanceMethods);
}
// |-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];
}
}
- (void)registerMetricsRecorder:(id<MetricsRecorder>)recorder
forSelector:(SEL)selector {
DCHECK(![self metricsRecorderForSelector:selector]);
_metricsRecorders[selector] = recorder;
}
- (void)deregisterMetricsRecordingForSelector:(SEL)selector {
_metricsRecorders.erase(selector);
}
#pragma mark - NSObject
// Overridden to forward messages to registered handlers.
- (id)forwardingTargetForSelector:(SEL)selector {
// If the selector is registered with a MetricsRecorder, return nil to force
// |forwardInvocation| to handle message forwarding. |forwardInvocation|
// provides an NSInvocation that is required by the MetricsRecorders.
if ([self metricsRecorderForSelector:selector])
return nil;
id target = [self targetForSelector:selector];
if (target)
return target;
return [super forwardingTargetForSelector:selector];
}
// Overriden to return YES for any registered method.
- (BOOL)respondsToSelector:(SEL)selector {
if ([self targetForSelector:selector])
return YES;
return [super respondsToSelector:selector];
}
// Overriden to forward messages to registered handlers when an NSInvocation is
// required.
- (void)forwardInvocation:(NSInvocation*)anInvocation {
SEL selector = anInvocation.selector;
id<MetricsRecorder> recorder = [self metricsRecorderForSelector:selector];
if (recorder) {
[recorder recordMetricForInvocation:anInvocation];
}
id target = [self targetForSelector:selector];
if ([target respondsToSelector:selector]) {
[anInvocation invokeWithTarget:target];
return;
}
[super forwardInvocation:anInvocation];
}
// Overriden because overrides of |forwardInvocation| also require an override
// of |methodSignatureForSelector|, as the method signature is needed to
// construct NSInvocations.
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature* signature = [super methodSignatureForSelector:aSelector];
if (signature)
return signature;
id target = [self targetForSelector:aSelector];
return [target methodSignatureForSelector:aSelector];
}
#pragma mark - Private
// Returns the target registered to receive messeages for |selector|.
- (id)targetForSelector:(SEL)selector {
auto target = _forwardingTargets.find(selector);
if (target == _forwardingTargets.end())
return nil;
return target->second;
}
// Returns the MetricsRecorder registered to be notified when |selector| is
// invoked on the dispatcher.
- (id<MetricsRecorder>)metricsRecorderForSelector:(SEL)selector {
auto recorder = _metricsRecorders.find(selector);
if (recorder == _metricsRecorders.end())
return nil;
return recorder->second;
}
@end