123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- //
- // Licensed under the terms in License.txt
- //
- // Copyright 2010 Allen Ding. All rights reserved.
- //
- #import "KWMatcherFactory.h"
- #import <objc/runtime.h>
- #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
|