Created
November 9, 2011 08:21
-
-
Save cppforlife/1350825 to your computer and use it in GitHub Desktop.
FakeRunLoop class useful in specs when code under test relies on NSRunLoop
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Foundation/Foundation.h> | |
typedef void(^VoidBlock)(void); | |
@interface FakeRunLoop : NSObject | |
+ (void)run:(VoidBlock)block; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "FakeRunLoop.h" | |
#import <objc/message.h> | |
#import <objc/runtime.h> | |
@interface FakeRunLoopCall : NSObject | |
@property (nonatomic, retain) id target; | |
@property (nonatomic, assign) SEL selector; | |
@property (nonatomic, retain) id object; | |
@end | |
@implementation FakeRunLoopCall | |
@synthesize target = target_, selector = selector_, object = object_; | |
- (void)dealloc { | |
self.target = nil; | |
self.object = nil; | |
[super dealloc]; | |
} | |
- (void)perform { | |
objc_msgSend(self.target, self.selector, self.object); | |
} | |
@end | |
@interface NSObject (Private) | |
+ (void)fakeRunLoop_enable:(BOOL)enable; | |
@end | |
@interface FakeRunLoop () | |
@property (nonatomic, retain) NSMutableArray *calls; | |
+ (FakeRunLoop *)sharedInstance; | |
- (void)performSelector:(SEL)aSelector target:(id)target withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes; | |
- (void)performSelector:(SEL)aSelector target:(id)target withObject:(id)anArgument afterDelay:(NSTimeInterval)delay; | |
@end | |
@implementation FakeRunLoop | |
@synthesize calls = calls_; | |
static FakeRunLoop *sharedFakeRunLoop = nil; | |
+ (FakeRunLoop *)sharedInstance { | |
if (!sharedFakeRunLoop) { | |
@throw @"Calling FakeRunLoop +sharedInstance when not allowed."; | |
} | |
return sharedFakeRunLoop; | |
} | |
+ (void)run:(VoidBlock)block { | |
@try { | |
sharedFakeRunLoop = [[FakeRunLoop alloc] init]; | |
[NSObject fakeRunLoop_enable:YES]; | |
block(); | |
while (sharedFakeRunLoop.calls.count > 0) { // Execute calls that block above might have queued up | |
[[sharedFakeRunLoop.calls objectAtIndex:0] perform]; | |
[sharedFakeRunLoop.calls removeObjectAtIndex:0]; | |
} | |
} @finally { | |
[NSObject fakeRunLoop_enable:NO]; | |
[sharedFakeRunLoop release]; | |
sharedFakeRunLoop = nil; | |
} | |
} | |
- (id)init { | |
if ((self = [super init])) { | |
self.calls = [NSMutableArray array]; | |
} | |
return self; | |
} | |
- (void)dealloc { | |
self.calls = nil; | |
[super dealloc]; | |
} | |
- (void)performSelector:(SEL)aSelector target:(id)target withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes { | |
FakeRunLoopCall *call = [[[FakeRunLoopCall alloc] init] autorelease]; | |
call.target = target; | |
call.selector = aSelector; | |
call.object = anArgument; | |
[self.calls addObject:call]; | |
} | |
- (void)performSelector:(SEL)aSelector target:(id)target withObject:(id)anArgument afterDelay:(NSTimeInterval)delay { | |
FakeRunLoopCall *call = [[[FakeRunLoopCall alloc] init] autorelease]; | |
call.target = target; | |
call.selector = aSelector; | |
call.object = anArgument; | |
[self.calls addObject:call]; | |
} | |
@end | |
@implementation NSObject (PerformSelectorSpecHelper) | |
void dk_swizzleInstanceMethod(Class class, SEL originalName, SEL newName) { | |
Method originalMethod = class_getInstanceMethod(class, originalName); | |
Method newMethod = class_getInstanceMethod(class, newName); | |
method_exchangeImplementations(originalMethod, newMethod); | |
} | |
void fakeRunLoop_swizzleInstanceMethodOn(SEL selector) { | |
NSString *oldSelectorName = [NSString stringWithFormat:@"fakeRunLoop_%@", NSStringFromSelector(selector)]; | |
dk_swizzleInstanceMethod([NSObject class], NSSelectorFromString(oldSelectorName), selector); | |
} | |
void fakeRunLoop_swizzleInstanceMethodOff(SEL selector) { | |
NSString *newSelectorName = [NSString stringWithFormat:@"fakeRunLoop_%@", NSStringFromSelector(selector)]; | |
dk_swizzleInstanceMethod([NSObject class], selector, NSSelectorFromString(newSelectorName)); | |
} | |
+ (void)fakeRunLoop_enable:(BOOL)enable { | |
// http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsobject_Class/Reference/Reference.html | |
void (*method)(SEL selector) = enable ? fakeRunLoop_swizzleInstanceMethodOn : fakeRunLoop_swizzleInstanceMethodOff; | |
method(@selector(performSelector:withObject:afterDelay:)); | |
method(@selector(performSelector:withObject:afterDelay:inModes:)); | |
method(@selector(performSelectorOnMainThread:withObject:waitUntilDone:)); | |
method(@selector(performSelectorOnMainThread:withObject:waitUntilDone:modes:)); | |
method(@selector(performSelector:onThread:withObject:waitUntilDone:)); | |
method(@selector(performSelector:onThread:withObject:waitUntilDone:modes:)); | |
method(@selector(performSelectorInBackground:withObject:)); | |
} | |
- (void)fakeRunLoop_performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay { | |
[[FakeRunLoop sharedInstance] performSelector:aSelector target:self withObject:anArgument afterDelay:delay]; | |
} | |
- (void)fakeRunLoop_performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes { | |
[[FakeRunLoop sharedInstance] performSelector:aSelector target:self withObject:anArgument afterDelay:delay inModes:modes]; | |
} | |
- (void)fakeRunLoop_performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait { | |
@throw @"Not Implemented."; | |
} | |
- (void)fakeRunLoop_performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array { | |
@throw @"Not Implemented."; | |
} | |
- (void)fakeRunLoop_performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait { | |
@throw @"Not Implemented."; | |
} | |
- (void)fakeRunLoop_performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array { | |
@throw @"Not Implemented."; | |
} | |
- (void)fakeRunLoop_performSelectorInBackground:(SEL)aSelector withObject:(id)arg { | |
@throw @"Not Implemented."; | |
} | |
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument { | |
@throw @"Not Implemented."; | |
} | |
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget { | |
@throw @"Not Implemented."; | |
} | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "SpecHelper.h" | |
#import "FakeRunLoop.h" | |
using namespace Cedar::Matchers; | |
@implementation NSObject (Spec) | |
- (void)someMethod:(NSString *)number { | |
NSLog(@"================> %@", number); | |
} | |
@end | |
SPEC_BEGIN(FakeRunLoopSpec) | |
describe(@"FakeRunLoop", ^ { | |
it(@"should run methods queued on run loop in correct order", ^{ | |
[FakeRunLoop run:^{ | |
NSLog(@"================> 1"); | |
[[NSObject alloc] performSelector:@selector(someMethod:) withObject:@"2"]; | |
NSLog(@"================> 3"); | |
[[NSObject alloc] performSelector:@selector(someMethod:) withObject:@"5" afterDelay:0]; | |
NSLog(@"================> 4"); | |
}]; | |
NSLog(@"================> 6"); | |
}); | |
}); | |
SPEC_END |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment