KWMatcherFactory.m 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. //
  2. // Licensed under the terms in License.txt
  3. //
  4. // Copyright 2010 Allen Ding. All rights reserved.
  5. //
  6. #import "KWMatcherFactory.h"
  7. #import <objc/runtime.h>
  8. #import "KWMatching.h"
  9. #import "KWStringUtilities.h"
  10. #import "KWUserDefinedMatcher.h"
  11. #import "KWMatchers.h"
  12. @interface KWMatcherFactory()
  13. @property (nonatomic, strong) NSMutableDictionary *matcherClassChains;
  14. @end
  15. @implementation KWMatcherFactory
  16. #pragma mark - Initializing
  17. - (id)init {
  18. self = [super init];
  19. if (self) {
  20. _matcherClassChains = [[NSMutableDictionary alloc] init];
  21. _registeredMatcherClasses = [[NSMutableArray alloc] init];
  22. }
  23. return self;
  24. }
  25. #pragma mark - Registering Matcher Classes
  26. - (void)registerMatcherClass:(Class)aClass {
  27. if ([self.registeredMatcherClasses containsObject:aClass])
  28. return;
  29. [(NSMutableArray *)self.registeredMatcherClasses addObject:aClass];
  30. for (NSString *verificationSelectorString in [aClass matcherStrings]) {
  31. NSMutableArray *matcherClassChain = self.matcherClassChains[verificationSelectorString];
  32. if (matcherClassChain == nil) {
  33. matcherClassChain = [[NSMutableArray alloc] init];
  34. self.matcherClassChains[verificationSelectorString] = matcherClassChain;
  35. }
  36. [matcherClassChain removeObject:aClass];
  37. [matcherClassChain insertObject:aClass atIndex:0];
  38. }
  39. }
  40. - (void)registerMatcherClassesWithNamespacePrefix:(NSString *)aNamespacePrefix {
  41. static NSMutableArray *matcherClasses = nil;
  42. // Cache all classes that conform to KWMatching.
  43. if (matcherClasses == nil) {
  44. matcherClasses = [[NSMutableArray alloc] init];
  45. int numberOfClasses = objc_getClassList(NULL, 0);
  46. Class *classes = (Class *)malloc(sizeof(Class) * numberOfClasses);
  47. numberOfClasses = objc_getClassList(classes, numberOfClasses);
  48. if (numberOfClasses == 0) {
  49. free(classes);
  50. return;
  51. }
  52. Protocol *kiwiMatching = @protocol(KWMatching);
  53. for (int i = 0; i < numberOfClasses; ++i) {
  54. Class candidateClass = classes[i];
  55. Class candidateOrSuper = candidateClass;
  56. // Don't use `NSObject#conformsToProtocol:` because it triggers `+initialize` methods to be called.
  57. while (candidateOrSuper != nil && class_conformsToProtocol(candidateOrSuper, kiwiMatching) == NO) {
  58. candidateOrSuper = class_getSuperclass(candidateOrSuper);
  59. }
  60. if (candidateOrSuper != nil) {
  61. [matcherClasses addObject:candidateClass];
  62. }
  63. }
  64. free(classes);
  65. }
  66. for (Class matcherClass in matcherClasses) {
  67. NSString *className = NSStringFromClass(matcherClass);
  68. if (KWStringHasStrictWordPrefix(className, aNamespacePrefix))
  69. [self registerMatcherClass:matcherClass];
  70. }
  71. }
  72. #pragma mark - Getting Method Signatures
  73. - (NSMethodSignature *)methodSignatureForMatcherSelector:(SEL)aSelector {
  74. NSMutableArray *matcherClassChain = self.matcherClassChains[NSStringFromSelector(aSelector)];
  75. if ([matcherClassChain count] == 0)
  76. return nil;
  77. Class matcherClass = matcherClassChain[0];
  78. return [matcherClass instanceMethodSignatureForSelector:aSelector];
  79. }
  80. #pragma mark - Getting Matchers
  81. - (KWMatcher *)matcherFromInvocation:(NSInvocation *)anInvocation subject:(id)subject {
  82. SEL selector = [anInvocation selector];
  83. // try and match a built-in or registered matcher class
  84. Class matcherClass = [self matcherClassForSelector:selector subject:subject];
  85. if (matcherClass == nil) {
  86. // see if we can match with a user-defined matcher instead
  87. return [[KWMatchers matchers] matcherForSelector:selector subject:subject];
  88. }
  89. return [[matcherClass alloc] initWithSubject:subject];
  90. }
  91. #pragma mark - Internal Methods
  92. - (Class)matcherClassForSelector:(SEL)aSelector subject:(id)anObject {
  93. NSArray *matcherClassChain = self.matcherClassChains[NSStringFromSelector(aSelector)];
  94. for (Class matcherClass in matcherClassChain) {
  95. if ([matcherClass canMatchSubject:anObject])
  96. return matcherClass;
  97. }
  98. return nil;
  99. }
  100. @end