summaryrefslogtreecommitdiffstats
path: root/base/ios/crb_protocol_observers.mm
blob: ee9e23fcb4e255c29ff0a33590027a25360d9d0e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// 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 "base/logging.h"
#include "base/mac/scoped_nsobject.h"

@interface CRBProtocolObservers ()

// Designated initializer.
- (id)initWithProtocol:(Protocol*)protocol;

@end

@implementation CRBProtocolObservers {
  base::scoped_nsobject<Protocol> _protocol;
  base::scoped_nsobject<NSHashTable> _observers;
}

+ (CRBProtocolObservers*)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]);
    _observers.reset([[NSHashTable weakObjectsHashTable] retain]);
  }
  return self;
}

- (Protocol*)protocol {
  return _protocol.get();
}

- (void)addObserver:(id)observer {
  DCHECK([observer conformsToProtocol:self.protocol]);
  [_observers addObject:observer];
}

- (void)removeObserver:(id)observer {
  [_observers removeObject:observer];
}

#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 {
  SEL selector = [invocation selector];
  base::scoped_nsobject<NSArray> observers([[_observers allObjects] retain]);
  for (id observer in observers.get()) {
    if ([observer respondsToSelector:selector])
      [invocation invokeWithTarget:observer];
  }
}

- (void)executeOnObservers:(ExecutionWithObserverBlock)callback {
  DCHECK(callback);
  base::scoped_nsobject<NSArray> observers([[_observers allObjects] retain]);
  for (id observer in observers.get()) {
    callback(observer);
  }
}

@end