RSSwizzle.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. //
  2. // RSSwizzle.m
  3. // RSSwizzleTests
  4. //
  5. // Created by Yan Rabovik on 05.09.13.
  6. //
  7. //
  8. #import "RSSwizzle.h"
  9. #import <objc/runtime.h>
  10. #include <dlfcn.h>
  11. #import <os/lock.h>
  12. #if !__has_feature(objc_arc)
  13. #error This code needs ARC. Use compiler option -fobjc-arc
  14. #endif
  15. #pragma mark - Block Helpers
  16. #if !defined(NS_BLOCK_ASSERTIONS)
  17. // See http://clang.llvm.org/docs/Block-ABI-Apple.html#high-level
  18. struct Block_literal_1 {
  19. void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
  20. int flags;
  21. int reserved;
  22. void (*invoke)(void *, ...);
  23. struct Block_descriptor_1 {
  24. unsigned long int reserved; // NULL
  25. unsigned long int size; // sizeof(struct Block_literal_1)
  26. // optional helper functions
  27. void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
  28. void (*dispose_helper)(void *src); // IFF (1<<25)
  29. // required ABI.2010.3.16
  30. const char *signature; // IFF (1<<30)
  31. } *descriptor;
  32. // imported variables
  33. };
  34. enum {
  35. BLOCK_HAS_COPY_DISPOSE = (1 << 25),
  36. BLOCK_HAS_CTOR = (1 << 26), // helpers have C++ code
  37. BLOCK_IS_GLOBAL = (1 << 28),
  38. BLOCK_HAS_STRET = (1 << 29), // IFF BLOCK_HAS_SIGNATURE
  39. BLOCK_HAS_SIGNATURE = (1 << 30),
  40. };
  41. typedef int BlockFlags;
  42. static const char *blockGetType(id block){
  43. struct Block_literal_1 *blockRef = (__bridge struct Block_literal_1 *)block;
  44. BlockFlags flags = blockRef->flags;
  45. if (flags & BLOCK_HAS_SIGNATURE) {
  46. void *signatureLocation = blockRef->descriptor;
  47. signatureLocation += sizeof(unsigned long int);
  48. signatureLocation += sizeof(unsigned long int);
  49. if (flags & BLOCK_HAS_COPY_DISPOSE) {
  50. signatureLocation += sizeof(void(*)(void *dst, void *src));
  51. signatureLocation += sizeof(void (*)(void *src));
  52. }
  53. const char *signature = (*(const char **)signatureLocation);
  54. return signature;
  55. }
  56. return NULL;
  57. }
  58. static BOOL blockIsCompatibleWithMethodType(id block, const char *methodType){
  59. const char *blockType = blockGetType(block);
  60. NSMethodSignature *blockSignature;
  61. if (0 == strncmp(blockType, (const char *)"@\"", 2)) {
  62. // Block return type includes class name for id types
  63. // while methodType does not include.
  64. // Stripping out return class name.
  65. char *quotePtr = strchr(blockType+2, '"');
  66. if (NULL != quotePtr) {
  67. ++quotePtr;
  68. size_t filterTypeLen = strlen(quotePtr) + 2;
  69. if (strlen(quotePtr) > filterTypeLen) { // integer overflow check
  70. NSCAssert(false, @"Method signature is too long to swizzle");
  71. return NO;
  72. }
  73. char filteredType[filterTypeLen];
  74. memset(filteredType, 0, sizeof(filteredType));
  75. *filteredType = '@';
  76. strncpy(filteredType + 1, quotePtr, sizeof(filteredType) - 2);
  77. blockSignature = [NSMethodSignature signatureWithObjCTypes:filteredType];
  78. }else{
  79. return NO;
  80. }
  81. }else{
  82. blockSignature = [NSMethodSignature signatureWithObjCTypes:blockType];
  83. }
  84. NSMethodSignature *methodSignature =
  85. [NSMethodSignature signatureWithObjCTypes:methodType];
  86. if (!blockSignature || !methodSignature) {
  87. return NO;
  88. }
  89. if (blockSignature.numberOfArguments != methodSignature.numberOfArguments){
  90. return NO;
  91. }
  92. if (strcmp(blockSignature.methodReturnType, methodSignature.methodReturnType) != 0) {
  93. return NO;
  94. }
  95. for (int i=0; i<methodSignature.numberOfArguments; ++i){
  96. if (i == 0){
  97. // self in method, block in block
  98. if (strcmp([methodSignature getArgumentTypeAtIndex:i], "@") != 0) {
  99. return NO;
  100. }
  101. if (strcmp([blockSignature getArgumentTypeAtIndex:i], "@?") != 0) {
  102. return NO;
  103. }
  104. }else if(i == 1){
  105. // SEL in method, self in block
  106. if (strcmp([methodSignature getArgumentTypeAtIndex:i], ":") != 0) {
  107. return NO;
  108. }
  109. if (strncmp([blockSignature getArgumentTypeAtIndex:i], "@", 1) != 0) {
  110. return NO;
  111. }
  112. }else {
  113. const char *blockSignatureArg = [blockSignature getArgumentTypeAtIndex:i];
  114. if (strncmp(blockSignatureArg, "@?", 2) == 0) {
  115. // Handle function pointer / block arguments
  116. blockSignatureArg = "@?";
  117. }
  118. else if (strncmp(blockSignatureArg, "@", 1) == 0) {
  119. blockSignatureArg = "@";
  120. }
  121. if (strcmp(blockSignatureArg,
  122. [methodSignature getArgumentTypeAtIndex:i]) != 0)
  123. {
  124. return NO;
  125. }
  126. }
  127. }
  128. return YES;
  129. }
  130. static BOOL blockIsAnImpFactoryBlock(id block){
  131. const char *blockType = blockGetType(block);
  132. RSSwizzleImpFactoryBlock dummyFactory = ^id(RSSwizzleInfo *swizzleInfo){
  133. return nil;
  134. };
  135. const char *factoryType = blockGetType(dummyFactory);
  136. return 0 == strcmp(factoryType, blockType);
  137. }
  138. #endif // NS_BLOCK_ASSERTIONS
  139. #pragma mark - Swizzling
  140. #pragma mark └ RSSwizzleInfo
  141. typedef IMP (^RSSWizzleImpProvider)(void);
  142. @interface RSSwizzleInfo()
  143. @property (nonatomic,copy) RSSWizzleImpProvider impProviderBlock;
  144. @property (nonatomic, readwrite) SEL selector;
  145. @end
  146. @implementation RSSwizzleInfo
  147. -(RSSwizzleOriginalIMP)getOriginalImplementation{
  148. NSAssert(_impProviderBlock,nil);
  149. // Casting IMP to RSSwizzleOriginalIMP to force user casting.
  150. return (RSSwizzleOriginalIMP)_impProviderBlock();
  151. }
  152. @end
  153. #pragma mark └ RSSwizzle
  154. @implementation RSSwizzle
  155. static void swizzle(Class classToSwizzle,
  156. SEL selector,
  157. RSSwizzleImpFactoryBlock factoryBlock)
  158. {
  159. Method method = class_getInstanceMethod(classToSwizzle, selector);
  160. NSCAssert(NULL != method,
  161. @"Selector %@ not found in %@ methods of class %@.",
  162. NSStringFromSelector(selector),
  163. class_isMetaClass(classToSwizzle) ? @"class" : @"instance",
  164. classToSwizzle);
  165. NSCAssert(blockIsAnImpFactoryBlock(factoryBlock),
  166. @"Wrong type of implementation factory block.");
  167. __block os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
  168. // To keep things thread-safe, we fill in the originalIMP later,
  169. // with the result of the class_replaceMethod call below.
  170. __block IMP originalIMP = NULL;
  171. // This block will be called by the client to get original implementation and call it.
  172. RSSWizzleImpProvider originalImpProvider = ^IMP{
  173. // It's possible that another thread can call the method between the call to
  174. // class_replaceMethod and its return value being set.
  175. // So to be sure originalIMP has the right value, we need a lock.
  176. os_unfair_lock_lock(&lock);
  177. IMP imp = originalIMP;
  178. os_unfair_lock_unlock(&lock);
  179. if (NULL == imp){
  180. // If the class does not implement the method
  181. // we need to find an implementation in one of the superclasses.
  182. Class superclass = class_getSuperclass(classToSwizzle);
  183. imp = method_getImplementation(class_getInstanceMethod(superclass,selector));
  184. }
  185. return imp;
  186. };
  187. RSSwizzleInfo *swizzleInfo = [RSSwizzleInfo new];
  188. swizzleInfo.selector = selector;
  189. swizzleInfo.impProviderBlock = originalImpProvider;
  190. // We ask the client for the new implementation block.
  191. // We pass swizzleInfo as an argument to factory block, so the client can
  192. // call original implementation from the new implementation.
  193. id newIMPBlock = factoryBlock(swizzleInfo);
  194. const char *methodType = method_getTypeEncoding(method);
  195. NSCAssert(blockIsCompatibleWithMethodType(newIMPBlock,methodType),
  196. @"Block returned from factory is not compatible with method type.");
  197. IMP newIMP = imp_implementationWithBlock(newIMPBlock);
  198. // Atomically replace the original method with our new implementation.
  199. // This will ensure that if someone else's code on another thread is messing
  200. // with the class' method list too, we always have a valid method at all times.
  201. //
  202. // If the class does not implement the method itself then
  203. // class_replaceMethod returns NULL and superclasses's implementation will be used.
  204. //
  205. // We need a lock to be sure that originalIMP has the right value in the
  206. // originalImpProvider block above.
  207. os_unfair_lock_lock(&lock);
  208. originalIMP = class_replaceMethod(classToSwizzle, selector, newIMP, methodType);
  209. os_unfair_lock_unlock(&lock);
  210. }
  211. static NSMutableDictionary *swizzledClassesDictionary(){
  212. static NSMutableDictionary *swizzledClasses;
  213. static dispatch_once_t onceToken;
  214. dispatch_once(&onceToken, ^{
  215. swizzledClasses = [NSMutableDictionary new];
  216. });
  217. return swizzledClasses;
  218. }
  219. static NSMutableSet *swizzledClassesForKey(const void *key){
  220. NSMutableDictionary *classesDictionary = swizzledClassesDictionary();
  221. NSValue *keyValue = [NSValue valueWithPointer:key];
  222. NSMutableSet *swizzledClasses = [classesDictionary objectForKey:keyValue];
  223. if (!swizzledClasses) {
  224. swizzledClasses = [NSMutableSet new];
  225. [classesDictionary setObject:swizzledClasses forKey:keyValue];
  226. }
  227. return swizzledClasses;
  228. }
  229. +(BOOL)swizzleInstanceMethod:(SEL)selector
  230. inClass:(Class)classToSwizzle
  231. newImpFactory:(RSSwizzleImpFactoryBlock)factoryBlock
  232. mode:(RSSwizzleMode)mode
  233. key:(const void *)key
  234. {
  235. NSAssert(!(NULL == key && RSSwizzleModeAlways != mode),
  236. @"Key may not be NULL if mode is not RSSwizzleModeAlways.");
  237. @synchronized(swizzledClassesDictionary()){
  238. if (key){
  239. NSSet *swizzledClasses = swizzledClassesForKey(key);
  240. if (mode == RSSwizzleModeOncePerClass) {
  241. if ([swizzledClasses containsObject:classToSwizzle]){
  242. return NO;
  243. }
  244. }else if (mode == RSSwizzleModeOncePerClassAndSuperclasses){
  245. for (Class currentClass = classToSwizzle;
  246. nil != currentClass;
  247. currentClass = class_getSuperclass(currentClass))
  248. {
  249. if ([swizzledClasses containsObject:currentClass]) {
  250. return NO;
  251. }
  252. }
  253. }
  254. }
  255. swizzle(classToSwizzle, selector, factoryBlock);
  256. if (key){
  257. [swizzledClassesForKey(key) addObject:classToSwizzle];
  258. }
  259. }
  260. return YES;
  261. }
  262. +(void)swizzleClassMethod:(SEL)selector
  263. inClass:(Class)classToSwizzle
  264. newImpFactory:(RSSwizzleImpFactoryBlock)factoryBlock
  265. {
  266. [self swizzleInstanceMethod:selector
  267. inClass:object_getClass(classToSwizzle)
  268. newImpFactory:factoryBlock
  269. mode:RSSwizzleModeAlways
  270. key:NULL];
  271. }
  272. @end