123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- //
- // RSSwizzle.m
- // RSSwizzleTests
- //
- // Created by Yan Rabovik on 05.09.13.
- //
- //
- #import "RSSwizzle.h"
- #import <objc/runtime.h>
- #include <dlfcn.h>
- #import <os/lock.h>
- #if !__has_feature(objc_arc)
- #error This code needs ARC. Use compiler option -fobjc-arc
- #endif
- #pragma mark - Block Helpers
- #if !defined(NS_BLOCK_ASSERTIONS)
- // See http://clang.llvm.org/docs/Block-ABI-Apple.html#high-level
- struct Block_literal_1 {
- void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
- int flags;
- int reserved;
- void (*invoke)(void *, ...);
- struct Block_descriptor_1 {
- unsigned long int reserved; // NULL
- unsigned long int size; // sizeof(struct Block_literal_1)
- // optional helper functions
- void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
- void (*dispose_helper)(void *src); // IFF (1<<25)
- // required ABI.2010.3.16
- const char *signature; // IFF (1<<30)
- } *descriptor;
- // imported variables
- };
- enum {
- BLOCK_HAS_COPY_DISPOSE = (1 << 25),
- BLOCK_HAS_CTOR = (1 << 26), // helpers have C++ code
- BLOCK_IS_GLOBAL = (1 << 28),
- BLOCK_HAS_STRET = (1 << 29), // IFF BLOCK_HAS_SIGNATURE
- BLOCK_HAS_SIGNATURE = (1 << 30),
- };
- typedef int BlockFlags;
- static const char *blockGetType(id block){
- struct Block_literal_1 *blockRef = (__bridge struct Block_literal_1 *)block;
- BlockFlags flags = blockRef->flags;
-
- if (flags & BLOCK_HAS_SIGNATURE) {
- void *signatureLocation = blockRef->descriptor;
- signatureLocation += sizeof(unsigned long int);
- signatureLocation += sizeof(unsigned long int);
-
- if (flags & BLOCK_HAS_COPY_DISPOSE) {
- signatureLocation += sizeof(void(*)(void *dst, void *src));
- signatureLocation += sizeof(void (*)(void *src));
- }
-
- const char *signature = (*(const char **)signatureLocation);
- return signature;
- }
-
- return NULL;
- }
- static BOOL blockIsCompatibleWithMethodType(id block, const char *methodType){
-
- const char *blockType = blockGetType(block);
-
- NSMethodSignature *blockSignature;
-
- if (0 == strncmp(blockType, (const char *)"@\"", 2)) {
- // Block return type includes class name for id types
- // while methodType does not include.
- // Stripping out return class name.
- char *quotePtr = strchr(blockType+2, '"');
- if (NULL != quotePtr) {
- ++quotePtr;
- size_t filterTypeLen = strlen(quotePtr) + 2;
- if (strlen(quotePtr) > filterTypeLen) { // integer overflow check
- NSCAssert(false, @"Method signature is too long to swizzle");
- return NO;
- }
- char filteredType[filterTypeLen];
- memset(filteredType, 0, sizeof(filteredType));
- *filteredType = '@';
- strncpy(filteredType + 1, quotePtr, sizeof(filteredType) - 2);
-
- blockSignature = [NSMethodSignature signatureWithObjCTypes:filteredType];
- }else{
- return NO;
- }
- }else{
- blockSignature = [NSMethodSignature signatureWithObjCTypes:blockType];
- }
-
- NSMethodSignature *methodSignature =
- [NSMethodSignature signatureWithObjCTypes:methodType];
-
- if (!blockSignature || !methodSignature) {
- return NO;
- }
-
- if (blockSignature.numberOfArguments != methodSignature.numberOfArguments){
- return NO;
- }
-
- if (strcmp(blockSignature.methodReturnType, methodSignature.methodReturnType) != 0) {
- return NO;
- }
-
- for (int i=0; i<methodSignature.numberOfArguments; ++i){
- if (i == 0){
- // self in method, block in block
- if (strcmp([methodSignature getArgumentTypeAtIndex:i], "@") != 0) {
- return NO;
- }
- if (strcmp([blockSignature getArgumentTypeAtIndex:i], "@?") != 0) {
- return NO;
- }
- }else if(i == 1){
- // SEL in method, self in block
- if (strcmp([methodSignature getArgumentTypeAtIndex:i], ":") != 0) {
- return NO;
- }
- if (strncmp([blockSignature getArgumentTypeAtIndex:i], "@", 1) != 0) {
- return NO;
- }
- }else {
- const char *blockSignatureArg = [blockSignature getArgumentTypeAtIndex:i];
-
- if (strncmp(blockSignatureArg, "@?", 2) == 0) {
- // Handle function pointer / block arguments
- blockSignatureArg = "@?";
- }
- else if (strncmp(blockSignatureArg, "@", 1) == 0) {
- blockSignatureArg = "@";
- }
-
- if (strcmp(blockSignatureArg,
- [methodSignature getArgumentTypeAtIndex:i]) != 0)
- {
- return NO;
- }
- }
- }
-
- return YES;
- }
- static BOOL blockIsAnImpFactoryBlock(id block){
- const char *blockType = blockGetType(block);
- RSSwizzleImpFactoryBlock dummyFactory = ^id(RSSwizzleInfo *swizzleInfo){
- return nil;
- };
- const char *factoryType = blockGetType(dummyFactory);
- return 0 == strcmp(factoryType, blockType);
- }
- #endif // NS_BLOCK_ASSERTIONS
- #pragma mark - Swizzling
- #pragma mark └ RSSwizzleInfo
- typedef IMP (^RSSWizzleImpProvider)(void);
- @interface RSSwizzleInfo()
- @property (nonatomic,copy) RSSWizzleImpProvider impProviderBlock;
- @property (nonatomic, readwrite) SEL selector;
- @end
- @implementation RSSwizzleInfo
- -(RSSwizzleOriginalIMP)getOriginalImplementation{
- NSAssert(_impProviderBlock,nil);
- // Casting IMP to RSSwizzleOriginalIMP to force user casting.
- return (RSSwizzleOriginalIMP)_impProviderBlock();
- }
- @end
- #pragma mark └ RSSwizzle
- @implementation RSSwizzle
- static void swizzle(Class classToSwizzle,
- SEL selector,
- RSSwizzleImpFactoryBlock factoryBlock)
- {
- Method method = class_getInstanceMethod(classToSwizzle, selector);
-
- NSCAssert(NULL != method,
- @"Selector %@ not found in %@ methods of class %@.",
- NSStringFromSelector(selector),
- class_isMetaClass(classToSwizzle) ? @"class" : @"instance",
- classToSwizzle);
-
- NSCAssert(blockIsAnImpFactoryBlock(factoryBlock),
- @"Wrong type of implementation factory block.");
-
- __block os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
-
- // To keep things thread-safe, we fill in the originalIMP later,
- // with the result of the class_replaceMethod call below.
- __block IMP originalIMP = NULL;
-
- // This block will be called by the client to get original implementation and call it.
- RSSWizzleImpProvider originalImpProvider = ^IMP{
- // It's possible that another thread can call the method between the call to
- // class_replaceMethod and its return value being set.
- // So to be sure originalIMP has the right value, we need a lock.
-
- os_unfair_lock_lock(&lock);
-
- IMP imp = originalIMP;
-
- os_unfair_lock_unlock(&lock);
-
- if (NULL == imp){
- // If the class does not implement the method
- // we need to find an implementation in one of the superclasses.
- Class superclass = class_getSuperclass(classToSwizzle);
- imp = method_getImplementation(class_getInstanceMethod(superclass,selector));
- }
- return imp;
- };
-
- RSSwizzleInfo *swizzleInfo = [RSSwizzleInfo new];
- swizzleInfo.selector = selector;
- swizzleInfo.impProviderBlock = originalImpProvider;
-
- // We ask the client for the new implementation block.
- // We pass swizzleInfo as an argument to factory block, so the client can
- // call original implementation from the new implementation.
- id newIMPBlock = factoryBlock(swizzleInfo);
-
- const char *methodType = method_getTypeEncoding(method);
-
- NSCAssert(blockIsCompatibleWithMethodType(newIMPBlock,methodType),
- @"Block returned from factory is not compatible with method type.");
-
- IMP newIMP = imp_implementationWithBlock(newIMPBlock);
-
- // Atomically replace the original method with our new implementation.
- // This will ensure that if someone else's code on another thread is messing
- // with the class' method list too, we always have a valid method at all times.
- //
- // If the class does not implement the method itself then
- // class_replaceMethod returns NULL and superclasses's implementation will be used.
- //
- // We need a lock to be sure that originalIMP has the right value in the
- // originalImpProvider block above.
-
- os_unfair_lock_lock(&lock);
-
- originalIMP = class_replaceMethod(classToSwizzle, selector, newIMP, methodType);
-
- os_unfair_lock_unlock(&lock);
- }
- static NSMutableDictionary *swizzledClassesDictionary(){
- static NSMutableDictionary *swizzledClasses;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- swizzledClasses = [NSMutableDictionary new];
- });
- return swizzledClasses;
- }
- static NSMutableSet *swizzledClassesForKey(const void *key){
- NSMutableDictionary *classesDictionary = swizzledClassesDictionary();
- NSValue *keyValue = [NSValue valueWithPointer:key];
- NSMutableSet *swizzledClasses = [classesDictionary objectForKey:keyValue];
- if (!swizzledClasses) {
- swizzledClasses = [NSMutableSet new];
- [classesDictionary setObject:swizzledClasses forKey:keyValue];
- }
- return swizzledClasses;
- }
- +(BOOL)swizzleInstanceMethod:(SEL)selector
- inClass:(Class)classToSwizzle
- newImpFactory:(RSSwizzleImpFactoryBlock)factoryBlock
- mode:(RSSwizzleMode)mode
- key:(const void *)key
- {
- NSAssert(!(NULL == key && RSSwizzleModeAlways != mode),
- @"Key may not be NULL if mode is not RSSwizzleModeAlways.");
-
- @synchronized(swizzledClassesDictionary()){
- if (key){
- NSSet *swizzledClasses = swizzledClassesForKey(key);
- if (mode == RSSwizzleModeOncePerClass) {
- if ([swizzledClasses containsObject:classToSwizzle]){
- return NO;
- }
- }else if (mode == RSSwizzleModeOncePerClassAndSuperclasses){
- for (Class currentClass = classToSwizzle;
- nil != currentClass;
- currentClass = class_getSuperclass(currentClass))
- {
- if ([swizzledClasses containsObject:currentClass]) {
- return NO;
- }
- }
- }
- }
-
- swizzle(classToSwizzle, selector, factoryBlock);
-
- if (key){
- [swizzledClassesForKey(key) addObject:classToSwizzle];
- }
- }
-
- return YES;
- }
- +(void)swizzleClassMethod:(SEL)selector
- inClass:(Class)classToSwizzle
- newImpFactory:(RSSwizzleImpFactoryBlock)factoryBlock
- {
- [self swizzleInstanceMethod:selector
- inClass:object_getClass(classToSwizzle)
- newImpFactory:factoryBlock
- mode:RSSwizzleModeAlways
- key:NULL];
- }
- @end
|