Skip to content

Instantly share code, notes, and snippets.

@hiroshi
Created April 15, 2011 07:11
Show Gist options
  • Save hiroshi/921289 to your computer and use it in GitHub Desktop.
Save hiroshi/921289 to your computer and use it in GitHub Desktop.
// -*- ObjC -*-
// Hiroshi Saito / Yakitara.com
/*
You know OCMock does great, but (If I'm not get wrong with it)
- it will supposed to be used in tests not a production code
- it can't mock class methods
- it requires an instace to be mocked accessible in your tests
If you not familiar with alias_method_chain, see:
https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/module/aliasing.rb
Examples
--------
// Example 1 (using category):
@implementation NSUserDefaults (Log)
+ (void)load {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self aliasClassMethod:@selector(standardUserDefaults) chainingPrefix:@"log"];
[pool release];
}
+ (NSUserDefaults *)log_standardUserDefaults {
NSLog(@"standardUserDefaults swizzled!");
return objc_msgSend(self, @selector(without_log_standardUserDefaults));
}
@end
// Example 2 (using block):
[self aliasClassMethod:@selector(standardUserDefaults) chainingPrefix:@"log" withBlock:^(id _class) {
NSLog(@"standardUserDefaults swizzled!");
return objc_msgSend(_class, @selector(without_log_standardUserDefaults));
}];
*/
@interface NSObject (AliasMethodChain)
+ (void)aliasClassMethod:(SEL)selector chainingPrefix:(NSString *)prefix;
+ (void)aliasClassMethod:(SEL)selector chainingPrefix:(NSString *)prefix withBlock:(void *)block;
+ (void)revertAliasClassMethod:(SEL)selector chainingPrefix:(NSString *)prefix;
+ (void)aliasInstanceMethod:(SEL)selector chainingPrefix:(NSString *)prefix;
+ (void)aliasInstanceMethod:(SEL)selector chainingPrefix:(NSString *)prefix withBlock:(void *)block;
+ (void)revertAliasInstanceMethod:(SEL)selector chainingPrefix:(NSString *)prefix;
@end
// Hiroshi Saito / Yakitara.com
#import "NSObject+AliasMethodChain.h"
#import <objc/runtime.h>
static
void aliasMethodChain(Class class, SEL selector, NSString *prefix, IMP newImp) {
Method method = class_isMetaClass(class) ? class_getClassMethod(class, selector) : class_getInstanceMethod(class, selector);
const char * types = method_getTypeEncoding(method);
const char* name = sel_getName(selector);
SEL newImpSelector = sel_registerName([[NSString stringWithFormat:@"%@_%s", prefix, name] UTF8String]);
if (newImp) {
class_addMethod(class, newImpSelector, newImp, types);
} else {
newImp = class_getMethodImplementation(class, newImpSelector);
}
IMP origImp = class_replaceMethod(class, selector, newImp, types);
SEL origImpSelector = sel_registerName([[NSString stringWithFormat:@"without_%@_%s", prefix, name] UTF8String]);
class_addMethod(class, origImpSelector, origImp, types);
}
static
void revertAliasMethodChain(Class class, SEL selector, NSString *prefix) {
Method method = class_isMetaClass(class) ? class_getClassMethod(class, selector) : class_getInstanceMethod(class, selector);
const char * types = method_getTypeEncoding(method);
SEL origImpSelector = sel_registerName([[NSString stringWithFormat:@"without_%@_%s", prefix, sel_getName(selector)] UTF8String]);
IMP origImp = class_getMethodImplementation(class, origImpSelector);
class_replaceMethod(class, selector, origImp, types);
}
#define _WORKAROUND_FOR_IMP_IMPLEMENTATION_WITH_BLOCK 1
#if _WORKAROUND_FOR_IMP_IMPLEMENTATION_WITH_BLOCK
static
void doWorkaroundFor_imp_implementationWithBlock(void *block) {
// NOTE: I don't know why, but in a certain condition, imp_implementationWithBlock() can cause a deadlock.
// NOTE: Copying the block will be a workaround for the issue.
// NOTE: Following lines are sample backtrace on a deadlock.
// #00x999430d6 in semaphore_wait_trap ()
// #10x012749e7 in _rwlock_read_nodebug ()
// #20x0126f07c in lookUpMethod ()
// #30x0126f1d6 in _class_lookupMethodAndLoadCache ()
// #40x012820e3 in objc_msgSend ()
// #50x0199cbf7 in _Block_object_assign ()
// #60x04d7d3a2 in __copy_helper_block_7 ()
// #70x0199c992 in _Block_copy_internal ()
// #80x0126d221 in imp_implementationWithBlock ()
[[(id)block copy] autorelease];
}
#else
#define doWorkaroundFor_imp_implementationWithBlock(block)
#endif
@implementation NSObject (AliasMethodChain)
+ (void)aliasClassMethod:(SEL)selector chainingPrefix:(NSString *)prefix {
aliasMethodChain(object_getClass(self), selector, prefix, nil);
}
+ (void)aliasClassMethod:(SEL)selector chainingPrefix:(NSString *)prefix withBlock:(void *)block {
doWorkaroundFor_imp_implementationWithBlock(block);
// CREDIT: http://www.friday.com/bbum/2011/03/17/ios-4-3-imp_implementationwithblock/
aliasMethodChain(object_getClass(self), selector, prefix, imp_implementationWithBlock(block));
}
+ (void)revertAliasClassMethod:(SEL)selector chainingPrefix:(NSString *)prefix {
revertAliasMethodChain(object_getClass(self), selector, prefix);
}
+ (void)aliasInstanceMethod:(SEL)selector chainingPrefix:(NSString *)prefix {
aliasMethodChain([self class], selector, prefix, nil);
}
+ (void)aliasInstanceMethod:(SEL)selector chainingPrefix:(NSString *)prefix withBlock:(void *)block {
doWorkaroundFor_imp_implementationWithBlock(block);
// CREDIT: http://www.friday.com/bbum/2011/03/17/ios-4-3-imp_implementationwithblock/
aliasMethodChain([self class], selector, prefix, imp_implementationWithBlock(block));
}
+ (void)revertAliasInstanceMethod:(SEL)selector chainingPrefix:(NSString *)prefix {
revertAliasMethodChain([self class], selector, prefix);
}
@end
@newacct
Copy link

newacct commented Jul 29, 2011

you don't need to do "[self class]" inside a class method. you can just use "self" because "self" is already the class object, and "[self class]" just returns the same class object

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment