// // Licensed under the terms in License.txt // // Copyright 2010 Allen Ding. All rights reserved. // #import "KWMatcherFactory.h" #import #import "KWMatching.h" #import "KWStringUtilities.h" #import "KWUserDefinedMatcher.h" #import "KWMatchers.h" @interface KWMatcherFactory() @property (nonatomic, strong) NSMutableDictionary *matcherClassChains; @end @implementation KWMatcherFactory #pragma mark - Initializing - (id)init { self = [super init]; if (self) { _matcherClassChains = [[NSMutableDictionary alloc] init]; _registeredMatcherClasses = [[NSMutableArray alloc] init]; } return self; } #pragma mark - Registering Matcher Classes - (void)registerMatcherClass:(Class)aClass { if ([self.registeredMatcherClasses containsObject:aClass]) return; [(NSMutableArray *)self.registeredMatcherClasses addObject:aClass]; for (NSString *verificationSelectorString in [aClass matcherStrings]) { NSMutableArray *matcherClassChain = self.matcherClassChains[verificationSelectorString]; if (matcherClassChain == nil) { matcherClassChain = [[NSMutableArray alloc] init]; self.matcherClassChains[verificationSelectorString] = matcherClassChain; } [matcherClassChain removeObject:aClass]; [matcherClassChain insertObject:aClass atIndex:0]; } } - (void)registerMatcherClassesWithNamespacePrefix:(NSString *)aNamespacePrefix { static NSMutableArray *matcherClasses = nil; // Cache all classes that conform to KWMatching. if (matcherClasses == nil) { matcherClasses = [[NSMutableArray alloc] init]; int numberOfClasses = objc_getClassList(NULL, 0); Class *classes = (Class *)malloc(sizeof(Class) * numberOfClasses); numberOfClasses = objc_getClassList(classes, numberOfClasses); if (numberOfClasses == 0) { free(classes); return; } Protocol *kiwiMatching = @protocol(KWMatching); for (int i = 0; i < numberOfClasses; ++i) { Class candidateClass = classes[i]; Class candidateOrSuper = candidateClass; // Don't use `NSObject#conformsToProtocol:` because it triggers `+initialize` methods to be called. while (candidateOrSuper != nil && class_conformsToProtocol(candidateOrSuper, kiwiMatching) == NO) { candidateOrSuper = class_getSuperclass(candidateOrSuper); } if (candidateOrSuper != nil) { [matcherClasses addObject:candidateClass]; } } free(classes); } for (Class matcherClass in matcherClasses) { NSString *className = NSStringFromClass(matcherClass); if (KWStringHasStrictWordPrefix(className, aNamespacePrefix)) [self registerMatcherClass:matcherClass]; } } #pragma mark - Getting Method Signatures - (NSMethodSignature *)methodSignatureForMatcherSelector:(SEL)aSelector { NSMutableArray *matcherClassChain = self.matcherClassChains[NSStringFromSelector(aSelector)]; if ([matcherClassChain count] == 0) return nil; Class matcherClass = matcherClassChain[0]; return [matcherClass instanceMethodSignatureForSelector:aSelector]; } #pragma mark - Getting Matchers - (KWMatcher *)matcherFromInvocation:(NSInvocation *)anInvocation subject:(id)subject { SEL selector = [anInvocation selector]; // try and match a built-in or registered matcher class Class matcherClass = [self matcherClassForSelector:selector subject:subject]; if (matcherClass == nil) { // see if we can match with a user-defined matcher instead return [[KWMatchers matchers] matcherForSelector:selector subject:subject]; } return [[matcherClass alloc] initWithSubject:subject]; } #pragma mark - Internal Methods - (Class)matcherClassForSelector:(SEL)aSelector subject:(id)anObject { NSArray *matcherClassChain = self.matcherClassChains[NSStringFromSelector(aSelector)]; for (Class matcherClass in matcherClassChain) { if ([matcherClass canMatchSubject:anObject]) return matcherClass; } return nil; } @end