Skip to content

Instantly share code, notes, and snippets.

@cppforlife
Created November 9, 2011 08:21
Show Gist options
  • Save cppforlife/1350825 to your computer and use it in GitHub Desktop.
Save cppforlife/1350825 to your computer and use it in GitHub Desktop.
FakeRunLoop class useful in specs when code under test relies on NSRunLoop
#import <Foundation/Foundation.h>
typedef void(^VoidBlock)(void);
@interface FakeRunLoop : NSObject
+ (void)run:(VoidBlock)block;
@end
#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
#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