Created
April 15, 2011 07:11
-
-
Save hiroshi/921289 to your computer and use it in GitHub Desktop.
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
// -*- 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 |
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
// 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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