1//--------------------------------------------------------------------------------------- 2// $Id$ 3// Copyright (c) 2009 by Mulle Kybernetik. See License file for details. 4//--------------------------------------------------------------------------------------- 5 6#import <objc/runtime.h> 7#import "OCPartialMockRecorder.h" 8#import "OCPartialMockObject.h" 9 10 11@interface OCPartialMockObject (Private) 12- (void)forwardInvocationForRealObject:(NSInvocation *)anInvocation; 13@end 14 15 16NSString *OCMRealMethodAliasPrefix = @"ocmock_replaced_"; 17 18@implementation OCPartialMockObject 19 20 21#pragma mark Mock table 22 23static NSMutableDictionary *mockTable; 24 25+ (void)initialize 26{ 27 if(self == [OCPartialMockObject class]) 28 mockTable = [[NSMutableDictionary alloc] init]; 29} 30 31+ (void)rememberPartialMock:(OCPartialMockObject *)mock forObject:(id)anObject 32{ 33 [mockTable setObject:[NSValue valueWithNonretainedObject:mock] forKey:[NSValue valueWithNonretainedObject:anObject]]; 34} 35 36+ (void)forgetPartialMockForObject:(id)anObject 37{ 38 [mockTable removeObjectForKey:[NSValue valueWithNonretainedObject:anObject]]; 39} 40 41+ (OCPartialMockObject *)existingPartialMockForObject:(id)anObject 42{ 43 OCPartialMockObject *mock = [[mockTable objectForKey:[NSValue valueWithNonretainedObject:anObject]] nonretainedObjectValue]; 44 if(mock == nil) 45 [NSException raise:NSInternalInconsistencyException format:@"No partial mock for object %p", anObject]; 46 return mock; 47} 48 49 50 51#pragma mark Initialisers, description, accessors, etc. 52 53- (id)initWithObject:(NSObject *)anObject 54{ 55 [super initWithClass:[anObject class]]; 56 realObject = [anObject retain]; 57 [[self class] rememberPartialMock:self forObject:anObject]; 58 [self setupSubclassForObject:realObject]; 59 return self; 60} 61 62- (void)dealloc 63{ 64 if(realObject != nil) 65 [self stop]; 66 [super dealloc]; 67} 68 69- (NSString *)description 70{ 71 return [NSString stringWithFormat:@"OCPartialMockObject[%@]", NSStringFromClass(mockedClass)]; 72} 73 74- (NSObject *)realObject 75{ 76 return realObject; 77} 78 79- (void)stop 80{ 81 object_setClass(realObject, [self mockedClass]); 82 [realObject release]; 83 [[self class] forgetPartialMockForObject:realObject]; 84 realObject = nil; 85} 86 87 88#pragma mark Subclass management 89 90- (void)setupSubclassForObject:(id)anObject 91{ 92 Class realClass = [anObject class]; 93 double timestamp = [NSDate timeIntervalSinceReferenceDate]; 94 const char *className = [[NSString stringWithFormat:@"%@-%p-%f", realClass, anObject, timestamp] UTF8String]; 95 Class subclass = objc_allocateClassPair(realClass, className, 0); 96 objc_registerClassPair(subclass); 97 object_setClass(anObject, subclass); 98 99 Method forwardInvocationMethod = class_getInstanceMethod([self class], @selector(forwardInvocationForRealObject:)); 100 IMP forwardInvocationImp = method_getImplementation(forwardInvocationMethod); 101 const char *forwardInvocationTypes = method_getTypeEncoding(forwardInvocationMethod); 102 class_addMethod(subclass, @selector(forwardInvocation:), forwardInvocationImp, forwardInvocationTypes); 103} 104 105- (void)setupForwarderForSelector:(SEL)selector 106{ 107 Class subclass = [[self realObject] class]; 108 Method originalMethod = class_getInstanceMethod([subclass superclass], selector); 109 IMP originalImp = method_getImplementation(originalMethod); 110 111 IMP forwarderImp = [subclass instanceMethodForSelector:@selector(aMethodThatMustNotExist)]; 112 class_addMethod(subclass, method_getName(originalMethod), forwarderImp, method_getTypeEncoding(originalMethod)); 113 114 SEL aliasSelector = NSSelectorFromString([OCMRealMethodAliasPrefix stringByAppendingString:NSStringFromSelector(selector)]); 115 class_addMethod(subclass, aliasSelector, originalImp, method_getTypeEncoding(originalMethod)); 116} 117 118- (void)forwardInvocationForRealObject:(NSInvocation *)anInvocation 119{ 120 // in here "self" is a reference to the real object, not the mock 121 OCPartialMockObject *mock = [OCPartialMockObject existingPartialMockForObject:self]; 122 if([mock handleInvocation:anInvocation] == NO) 123 [NSException raise:NSInternalInconsistencyException format:@"Ended up in subclass forwarder for %@ with unstubbed method %@", 124 [self class], NSStringFromSelector([anInvocation selector])]; 125} 126 127 128 129#pragma mark Overrides 130 131- (id)getNewRecorder 132{ 133 return [[[OCPartialMockRecorder alloc] initWithSignatureResolver:self] autorelease]; 134} 135 136- (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation 137{ 138 [anInvocation invokeWithTarget:realObject]; 139} 140 141 142@end 143