├── .gitignore ├── LICENSE ├── README.md ├── RSSwizzle.podspec ├── RSSwizzle ├── RSSwizzle.h └── RSSwizzle.m └── RSSwizzleTests ├── RSSwizzleTests-OSX ├── RSSwizzleTests-OSX-Info.plist ├── RSSwizzleTests-OSX-Prefix.pch └── en.lproj │ └── InfoPlist.strings ├── RSSwizzleTests-iOS ├── RSSwizzleTests-iOS-Info.plist ├── RSSwizzleTests-iOS-Prefix.pch └── en.lproj │ └── InfoPlist.strings ├── RSSwizzleTests.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── RSSwizzleTests ├── RSSwizzleTests.m ├── RSTestsLog.h └── RSTestsLog.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | *.xccheckout 19 | 20 | #CocoaPods 21 | Pods 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Yan Rabovik 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # RSSwizzle 3 | Safe method swizzling done right. 4 | 5 | ## Motivation 6 | 7 | Classical method swizzling with `method_exchangeImplementations` is quite simple, but it has a lot of limitations: 8 | 9 | * It is safe only if swizzling is done in the `+load` method. If you need to swizzle methods during application's lifetime you should take into account that third-party code may do swizzling of the same method in another thread at the same time. 10 | * The swizzled method must be implemented by the class itself and not by superclasses. Workarounds by copying implementation from the superclass do not really work. Original implementation in the superclass must be fetched at the time of calling, not at the time of swizzling ([1][774],[2][775]). 11 | * The swizzled method implementation must not rely on the `_cmd` argument. _(And generally you can not be sure in it ([5][cmd]).)_ 12 | * Naming conflicts are possible ([3][SO]). 13 | 14 | For more details see discussions in: [1][774], [2][775], [3][SO], [4][TH], [5][cmd]. 15 | 16 | **RSSwizzle** avoids all these known pitfalls. 17 | 18 | ## Usage 19 | 20 | Original implementation must always be called from the new implementation. And because of the the fact that for safe and robust swizzling original implementation must be dynamically fetched at the time of calling and not at the time of swizzling ([1][774],[2][775]), swizzling API is slightly unusual. 21 | 22 | Example for swizzling `-(int)calculate:(int)number;` method: 23 | 24 | ```objective-c 25 | RSSwizzleInstanceMethod(classToSwizzle, 26 | @selector(calculate:), 27 | RSSWReturnType(int), 28 | RSSWArguments(int number), 29 | RSSWReplacement( 30 | { 31 | // The following code will be used as the new implementation. 32 | 33 | // Calling original implementation. 34 | int res = RSSWCallOriginal(number); 35 | // Returning modified return value. 36 | return res + 1; 37 | }), 0, NULL); 38 | ``` 39 | 40 | #### Alternative API 41 | 42 | Alternatively you may use an API without macros, though it is a little bit complicated. 43 | 44 | You should pass a factory block that returns the block for the new implementation of the swizzled method. And use `swizzleInfo` argument to retrieve and call original implementation. 45 | 46 | Example for swizzling `-(int)calculate:(int)number;` method: 47 | 48 | ```objective-c 49 | SEL selector = @selector(calculate:); 50 | [RSSwizzle 51 | swizzleInstanceMethod:selector 52 | inClass:classToSwizzle 53 | newImpFactory:^id(RSSwizzleInfo *swizzleInfo) { 54 | // This block will be used as the new implementation. 55 | return ^int(__unsafe_unretained id self, int num){ 56 | // You MUST always cast implementation to the correct function pointer. 57 | int (*originalIMP)(__unsafe_unretained id, SEL, int); 58 | originalIMP = (__typeof(originalIMP))[swizzleInfo getOriginalImplementation]; 59 | // Calling original implementation. 60 | int res = originalIMP(self,selector,num); 61 | // Returning modified return value. 62 | return res + 1; 63 | }; 64 | } 65 | mode:RSSwizzleModeAlways 66 | key:NULL]; 67 | ``` 68 | 69 | #### Class Method Swizzling 70 | Class method swizzling is done with a similar API. 71 | 72 | Example for swizzling `+(int)calculate:(int)number;` method: 73 | 74 | ```objective-c 75 | RSSwizzleClassMethod(classToSwizzle, 76 | @selector(calculate:), 77 | RSSWReturnType(int), 78 | RSSWArguments(int number), 79 | RSSWReplacement( 80 | { 81 | // Calling original implementation. 82 | int res = RSSWCallOriginal(number); 83 | // Returning modified return value. 84 | return res + 1; 85 | })); 86 | ``` 87 | 88 | 89 | #### Modes 90 | 91 | Swizzling frequently goes along with checking whether this particular class (or one of its superclasses) has been already swizzled. Here the `mode` and `key` parameters can help. 92 | Possible mode values: 93 | 94 | * `RSSwizzleModeAlways` **RSSwizzle** always does swizzling regardless of the given `key`. 95 | * `RSSwizzleModeOncePerClass` **RSSwizzle** does not do swizzling if the same class has been swizzled earlier with the same `key`. 96 | * `RSSwizzleModeOncePerClassAndSuperclasses` **RSSwizzle** does not do swizzling if the same class or one of its superclasses have been swizzled earlier with the same `key`. 97 | 98 | Here is an example of swizzling `-(void)dealloc;` only in case when neither class and no one of its superclasses has been already swizzled with the given `key`: 99 | 100 | ```objective-c 101 | static const void *key = &key; 102 | SEL selector = NSSelectorFromString(@"dealloc"); 103 | RSSwizzleInstanceMethod(classToSwizzle, 104 | selector, 105 | RSSWReturnType(void), 106 | RSSWArguments(), 107 | RSSWReplacement( 108 | { 109 | NSLog(@"Deallocating %@.",self); 110 | RSSWCallOriginal(); 111 | }), RSSwizzleModeOncePerClassAndSuperclasses, key); 112 | ``` 113 | 114 | > **Note:** `RSSwizzleModeOncePerClassAndSuperclasses ` mode does not guarantees that your implementation will be called only once per method call. If the order of swizzling is: first inherited class, second superclass; then both swizzlings will be done and the new implementation will be called twice. 115 | 116 | #### Thread safety 117 | 118 | **RSSwizzle** is fully thread safe. You do not need any additional synchronization. 119 | 120 | ## CocoaPods 121 | Add `RSSwizzle` to your _Podfile_. 122 | 123 | ## Requirements 124 | * iOS 5.0+ 125 | * Mac OS X 10.7+ 126 | * ARC 127 | 128 | ## Author 129 | Yan Rabovik ([@rabovik][twitter] on twitter) 130 | 131 | ## License 132 | MIT License. 133 | 134 | [twitter]: https://twitter.com/rabovik 135 | [cmd]: http://www.mikeash.com/pyblog/friday-qa-2010-01-29-method-replacement-for-fun-and-profit.html#comment-e2c2af6395d9e8fca559895bbd434ee8 136 | [SO]: http://stackoverflow.com/a/8636521/441735 137 | [774]: https://github.com/ReactiveCocoa/ReactiveCocoa/pull/774 138 | [775]: https://github.com/ReactiveCocoa/ReactiveCocoa/pull/775 139 | [TH]: https://github.com/th-in-gs/THObserversAndBinders/commit/cabe12dece2faabf5e58759363ac603be963c889#L1R231 -------------------------------------------------------------------------------- /RSSwizzle.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "RSSwizzle" 3 | s.version = "0.1.0" 4 | s.summary = "Safe method swizzling done right." 5 | s.homepage = "https://github.com/rabovik/RSSwizzle" 6 | s.license = 'MIT' 7 | s.author = { "Yan Rabovik" => "yan@rabovik.ru" } 8 | s.source = { :git => "https://github.com/rabovik/RSSwizzle.git", :tag => "#{s.version}" } 9 | s.ios.deployment_target = '5.0' 10 | s.osx.deployment_target = '10.7' 11 | s.source_files = 'RSSwizzle', 'RSSwizzle/**/*.{h,m}' 12 | s.frameworks = 'Foundation' 13 | s.requires_arc = true 14 | end 15 | -------------------------------------------------------------------------------- /RSSwizzle/RSSwizzle.h: -------------------------------------------------------------------------------- 1 | // 2 | // RSSwizzle.h 3 | // RSSwizzleTests 4 | // 5 | // Created by Yan Rabovik on 05.09.13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | #pragma mark - Macros Based API 12 | 13 | /// A macro for wrapping the return type of the swizzled method. 14 | #define RSSWReturnType(type) type 15 | 16 | /// A macro for wrapping arguments of the swizzled method. 17 | #define RSSWArguments(arguments...) _RSSWArguments(arguments) 18 | 19 | /// A macro for wrapping the replacement code for the swizzled method. 20 | #define RSSWReplacement(code...) code 21 | 22 | /// A macro for casting and calling original implementation. 23 | /// May be used only in RSSwizzleInstanceMethod or RSSwizzleClassMethod macros. 24 | #define RSSWCallOriginal(arguments...) _RSSWCallOriginal(arguments) 25 | 26 | #pragma mark └ Swizzle Instance Method 27 | 28 | /** 29 | Swizzles the instance method of the class with the new implementation. 30 | 31 | Example for swizzling `-(int)calculate:(int)number;` method: 32 | 33 | @code 34 | 35 | RSSwizzleInstanceMethod(classToSwizzle, 36 | @selector(calculate:), 37 | RSSWReturnType(int), 38 | RSSWArguments(int number), 39 | RSSWReplacement( 40 | { 41 | // Calling original implementation. 42 | int res = RSSWCallOriginal(number); 43 | // Returning modified return value. 44 | return res + 1; 45 | }), 0, NULL); 46 | 47 | @endcode 48 | 49 | Swizzling frequently goes along with checking whether this particular class (or one of its superclasses) has been already swizzled. Here the `RSSwizzleMode` and `key` parameters can help. See +[RSSwizzle swizzleInstanceMethod:inClass:newImpFactory:mode:key:] for details. 50 | 51 | Swizzling is fully thread-safe. 52 | 53 | @param classToSwizzle The class with the method that should be swizzled. 54 | 55 | @param selector Selector of the method that should be swizzled. 56 | 57 | @param RSSWReturnType The return type of the swizzled method wrapped in the RSSWReturnType macro. 58 | 59 | @param RSSWArguments The arguments of the swizzled method wrapped in the RSSWArguments macro. 60 | 61 | @param RSSWReplacement The code of the new implementation of the swizzled method wrapped in the RSSWReplacement macro. 62 | 63 | @param RSSwizzleMode The mode is used in combination with the key to indicate whether the swizzling should be done for the given class. You can pass 0 for RSSwizzleModeAlways. 64 | 65 | @param key The key is used in combination with the mode to indicate whether the swizzling should be done for the given class. May be NULL if the mode is RSSwizzleModeAlways. 66 | 67 | @return YES if successfully swizzled and NO if swizzling has been already done for given key and class (or one of superclasses, depends on the mode). 68 | 69 | */ 70 | #define RSSwizzleInstanceMethod(classToSwizzle, \ 71 | selector, \ 72 | RSSWReturnType, \ 73 | RSSWArguments, \ 74 | RSSWReplacement, \ 75 | RSSwizzleMode, \ 76 | key) \ 77 | _RSSwizzleInstanceMethod(classToSwizzle, \ 78 | selector, \ 79 | RSSWReturnType, \ 80 | _RSSWWrapArg(RSSWArguments), \ 81 | _RSSWWrapArg(RSSWReplacement), \ 82 | RSSwizzleMode, \ 83 | key) 84 | 85 | #pragma mark └ Swizzle Class Method 86 | 87 | /** 88 | Swizzles the class method of the class with the new implementation. 89 | 90 | Example for swizzling `+(int)calculate:(int)number;` method: 91 | 92 | @code 93 | 94 | RSSwizzleClassMethod(classToSwizzle, 95 | @selector(calculate:), 96 | RSSWReturnType(int), 97 | RSSWArguments(int number), 98 | RSSWReplacement( 99 | { 100 | // Calling original implementation. 101 | int res = RSSWCallOriginal(number); 102 | // Returning modified return value. 103 | return res + 1; 104 | })); 105 | 106 | @endcode 107 | 108 | Swizzling is fully thread-safe. 109 | 110 | @param classToSwizzle The class with the method that should be swizzled. 111 | 112 | @param selector Selector of the method that should be swizzled. 113 | 114 | @param RSSWReturnType The return type of the swizzled method wrapped in the RSSWReturnType macro. 115 | 116 | @param RSSWArguments The arguments of the swizzled method wrapped in the RSSWArguments macro. 117 | 118 | @param RSSWReplacement The code of the new implementation of the swizzled method wrapped in the RSSWReplacement macro. 119 | 120 | */ 121 | #define RSSwizzleClassMethod(classToSwizzle, \ 122 | selector, \ 123 | RSSWReturnType, \ 124 | RSSWArguments, \ 125 | RSSWReplacement) \ 126 | _RSSwizzleClassMethod(classToSwizzle, \ 127 | selector, \ 128 | RSSWReturnType, \ 129 | _RSSWWrapArg(RSSWArguments), \ 130 | _RSSWWrapArg(RSSWReplacement)) 131 | 132 | #pragma mark - Main API 133 | 134 | /** 135 | A function pointer to the original implementation of the swizzled method. 136 | */ 137 | typedef void (*RSSwizzleOriginalIMP)(void /* id, SEL, ... */ ); 138 | 139 | /** 140 | RSSwizzleInfo is used in the new implementation block to get and call original implementation of the swizzled method. 141 | */ 142 | @interface RSSwizzleInfo : NSObject 143 | 144 | /** 145 | Returns the original implementation of the swizzled method. 146 | 147 | It is actually either an original implementation if the swizzled class implements the method itself; or a super implementation fetched from one of the superclasses. 148 | 149 | @note You must always cast returned implementation to the appropriate function pointer when calling. 150 | 151 | @return A function pointer to the original implementation of the swizzled method. 152 | */ 153 | -(RSSwizzleOriginalIMP)getOriginalImplementation; 154 | 155 | /// The selector of the swizzled method. 156 | @property (nonatomic, readonly) SEL selector; 157 | 158 | @end 159 | 160 | /** 161 | A factory block returning the block for the new implementation of the swizzled method. 162 | 163 | You must always obtain original implementation with swizzleInfo and call it from the new implementation. 164 | 165 | @param swizzleInfo An info used to get and call the original implementation of the swizzled method. 166 | 167 | @return A block that implements a method. 168 | Its signature should be: `method_return_type ^(id self, method_args...)`. 169 | The selector is not available as a parameter to this block. 170 | */ 171 | typedef id (^RSSwizzleImpFactoryBlock)(RSSwizzleInfo *swizzleInfo); 172 | 173 | typedef NS_ENUM(NSUInteger, RSSwizzleMode) { 174 | /// RSSwizzle always does swizzling. 175 | RSSwizzleModeAlways = 0, 176 | /// RSSwizzle does not do swizzling if the same class has been swizzled earlier with the same key. 177 | RSSwizzleModeOncePerClass = 1, 178 | /// RSSwizzle does not do swizzling if the same class or one of its superclasses have been swizzled earlier with the same key. 179 | /// @note There is no guarantee that your implementation will be called only once per method call. If the order of swizzling is: first inherited class, second superclass, then both swizzlings will be done and the new implementation will be called twice. 180 | RSSwizzleModeOncePerClassAndSuperclasses = 2 181 | }; 182 | 183 | @interface RSSwizzle : NSObject 184 | 185 | #pragma mark └ Swizzle Instance Method 186 | 187 | /** 188 | Swizzles the instance method of the class with the new implementation. 189 | 190 | Original implementation must always be called from the new implementation. And because of the the fact that for safe and robust swizzling original implementation must be dynamically fetched at the time of calling and not at the time of swizzling, swizzling API is a little bit complicated. 191 | 192 | You should pass a factory block that returns the block for the new implementation of the swizzled method. And use swizzleInfo argument to retrieve and call original implementation. 193 | 194 | Example for swizzling `-(int)calculate:(int)number;` method: 195 | 196 | @code 197 | 198 | SEL selector = @selector(calculate:); 199 | [RSSwizzle 200 | swizzleInstanceMethod:selector 201 | inClass:classToSwizzle 202 | newImpFactory:^id(RSSWizzleInfo *swizzleInfo) { 203 | // This block will be used as the new implementation. 204 | return ^int(__unsafe_unretained id self, int num){ 205 | // You MUST always cast implementation to the correct function pointer. 206 | int (*originalIMP)(__unsafe_unretained id, SEL, int); 207 | originalIMP = (__typeof(originalIMP))[swizzleInfo getOriginalImplementation]; 208 | // Calling original implementation. 209 | int res = originalIMP(self,selector,num); 210 | // Returning modified return value. 211 | return res + 1; 212 | }; 213 | } 214 | mode:RSSwizzleModeAlways 215 | key:NULL]; 216 | 217 | @endcode 218 | 219 | Swizzling frequently goes along with checking whether this particular class (or one of its superclasses) has been already swizzled. Here the `mode` and `key` parameters can help. 220 | 221 | Here is an example of swizzling `-(void)dealloc;` only in case when neither class and no one of its superclasses has been already swizzled with our key. However "Deallocating ..." message still may be logged multiple times per method call if swizzling was called primarily for an inherited class and later for one of its superclasses. 222 | 223 | @code 224 | 225 | static const void *key = &key; 226 | SEL selector = NSSelectorFromString(@"dealloc"); 227 | [RSSwizzle 228 | swizzleInstanceMethod:selector 229 | inClass:classToSwizzle 230 | newImpFactory:^id(RSSWizzleInfo *swizzleInfo) { 231 | return ^void(__unsafe_unretained id self){ 232 | NSLog(@"Deallocating %@.",self); 233 | 234 | void (*originalIMP)(__unsafe_unretained id, SEL); 235 | originalIMP = (__typeof(originalIMP))[swizzleInfo getOriginalImplementation]; 236 | originalIMP(self,selector); 237 | }; 238 | } 239 | mode:RSSwizzleModeOncePerClassAndSuperclasses 240 | key:key]; 241 | 242 | @endcode 243 | 244 | Swizzling is fully thread-safe. 245 | 246 | @param selector Selector of the method that should be swizzled. 247 | 248 | @param classToSwizzle The class with the method that should be swizzled. 249 | 250 | @param factoryBlock The factory block returning the block for the new implementation of the swizzled method. 251 | 252 | @param mode The mode is used in combination with the key to indicate whether the swizzling should be done for the given class. 253 | 254 | @param key The key is used in combination with the mode to indicate whether the swizzling should be done for the given class. May be NULL if the mode is RSSwizzleModeAlways. 255 | 256 | @return YES if successfully swizzled and NO if swizzling has been already done for given key and class (or one of superclasses, depends on the mode). 257 | */ 258 | +(BOOL)swizzleInstanceMethod:(SEL)selector 259 | inClass:(Class)classToSwizzle 260 | newImpFactory:(RSSwizzleImpFactoryBlock)factoryBlock 261 | mode:(RSSwizzleMode)mode 262 | key:(const void *)key; 263 | 264 | #pragma mark └ Swizzle Class method 265 | 266 | /** 267 | Swizzles the class method of the class with the new implementation. 268 | 269 | Original implementation must always be called from the new implementation. And because of the the fact that for safe and robust swizzling original implementation must be dynamically fetched at the time of calling and not at the time of swizzling, swizzling API is a little bit complicated. 270 | 271 | You should pass a factory block that returns the block for the new implementation of the swizzled method. And use swizzleInfo argument to retrieve and call original implementation. 272 | 273 | Example for swizzling `+(int)calculate:(int)number;` method: 274 | 275 | @code 276 | 277 | SEL selector = @selector(calculate:); 278 | [RSSwizzle 279 | swizzleClassMethod:selector 280 | inClass:classToSwizzle 281 | newImpFactory:^id(RSSWizzleInfo *swizzleInfo) { 282 | // This block will be used as the new implementation. 283 | return ^int(__unsafe_unretained id self, int num){ 284 | // You MUST always cast implementation to the correct function pointer. 285 | int (*originalIMP)(__unsafe_unretained id, SEL, int); 286 | originalIMP = (__typeof(originalIMP))[swizzleInfo getOriginalImplementation]; 287 | // Calling original implementation. 288 | int res = originalIMP(self,selector,num); 289 | // Returning modified return value. 290 | return res + 1; 291 | }; 292 | }]; 293 | 294 | @endcode 295 | 296 | Swizzling is fully thread-safe. 297 | 298 | @param selector Selector of the method that should be swizzled. 299 | 300 | @param classToSwizzle The class with the method that should be swizzled. 301 | 302 | @param factoryBlock The factory block returning the block for the new implementation of the swizzled method. 303 | */ 304 | +(void)swizzleClassMethod:(SEL)selector 305 | inClass:(Class)classToSwizzle 306 | newImpFactory:(RSSwizzleImpFactoryBlock)factoryBlock; 307 | 308 | @end 309 | 310 | #pragma mark - Implementation details 311 | // Do not write code that depends on anything below this line. 312 | 313 | // Wrapping arguments to pass them as a single argument to another macro. 314 | #define _RSSWWrapArg(args...) args 315 | 316 | #define _RSSWDel2Arg(a1, a2, args...) a1, ##args 317 | #define _RSSWDel3Arg(a1, a2, a3, args...) a1, a2, ##args 318 | 319 | // To prevent comma issues if there are no arguments we add one dummy argument 320 | // and remove it later. 321 | #define _RSSWArguments(arguments...) DEL, ##arguments 322 | 323 | #define _RSSwizzleInstanceMethod(classToSwizzle, \ 324 | selector, \ 325 | RSSWReturnType, \ 326 | RSSWArguments, \ 327 | RSSWReplacement, \ 328 | RSSwizzleMode, \ 329 | KEY) \ 330 | [RSSwizzle \ 331 | swizzleInstanceMethod:selector \ 332 | inClass:[classToSwizzle class] \ 333 | newImpFactory:^id(RSSwizzleInfo *swizzleInfo) { \ 334 | RSSWReturnType (*originalImplementation_)(_RSSWDel3Arg(__unsafe_unretained id, \ 335 | SEL, \ 336 | RSSWArguments)); \ 337 | SEL selector_ = selector; \ 338 | return ^RSSWReturnType (_RSSWDel2Arg(__unsafe_unretained id self, \ 339 | RSSWArguments)) \ 340 | { \ 341 | RSSWReplacement \ 342 | }; \ 343 | } \ 344 | mode:RSSwizzleMode \ 345 | key:KEY]; 346 | 347 | #define _RSSwizzleClassMethod(classToSwizzle, \ 348 | selector, \ 349 | RSSWReturnType, \ 350 | RSSWArguments, \ 351 | RSSWReplacement) \ 352 | [RSSwizzle \ 353 | swizzleClassMethod:selector \ 354 | inClass:[classToSwizzle class] \ 355 | newImpFactory:^id(RSSwizzleInfo *swizzleInfo) { \ 356 | RSSWReturnType (*originalImplementation_)(_RSSWDel3Arg(__unsafe_unretained id, \ 357 | SEL, \ 358 | RSSWArguments)); \ 359 | SEL selector_ = selector; \ 360 | return ^RSSWReturnType (_RSSWDel2Arg(__unsafe_unretained id self, \ 361 | RSSWArguments)) \ 362 | { \ 363 | RSSWReplacement \ 364 | }; \ 365 | }]; 366 | 367 | #define _RSSWCallOriginal(arguments...) \ 368 | ((__typeof(originalImplementation_))[swizzleInfo \ 369 | getOriginalImplementation])(self, \ 370 | selector_, \ 371 | ##arguments) 372 | -------------------------------------------------------------------------------- /RSSwizzle/RSSwizzle.m: -------------------------------------------------------------------------------- 1 | // 2 | // RSSwizzle.m 3 | // RSSwizzleTests 4 | // 5 | // Created by Yan Rabovik on 05.09.13. 6 | // 7 | // 8 | 9 | #import "RSSwizzle.h" 10 | #import 11 | #import 12 | 13 | #if !__has_feature(objc_arc) 14 | #error This code needs ARC. Use compiler option -fobjc-arc 15 | #endif 16 | 17 | #pragma mark - Block Helpers 18 | #if !defined(NS_BLOCK_ASSERTIONS) 19 | 20 | // See http://clang.llvm.org/docs/Block-ABI-Apple.html#high-level 21 | struct Block_literal_1 { 22 | void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock 23 | int flags; 24 | int reserved; 25 | void (*invoke)(void *, ...); 26 | struct Block_descriptor_1 { 27 | unsigned long int reserved; // NULL 28 | unsigned long int size; // sizeof(struct Block_literal_1) 29 | // optional helper functions 30 | void (*copy_helper)(void *dst, void *src); // IFF (1<<25) 31 | void (*dispose_helper)(void *src); // IFF (1<<25) 32 | // required ABI.2010.3.16 33 | const char *signature; // IFF (1<<30) 34 | } *descriptor; 35 | // imported variables 36 | }; 37 | 38 | enum { 39 | BLOCK_HAS_COPY_DISPOSE = (1 << 25), 40 | BLOCK_HAS_CTOR = (1 << 26), // helpers have C++ code 41 | BLOCK_IS_GLOBAL = (1 << 28), 42 | BLOCK_HAS_STRET = (1 << 29), // IFF BLOCK_HAS_SIGNATURE 43 | BLOCK_HAS_SIGNATURE = (1 << 30), 44 | }; 45 | typedef int BlockFlags; 46 | 47 | static const char *blockGetType(id block){ 48 | struct Block_literal_1 *blockRef = (__bridge struct Block_literal_1 *)block; 49 | BlockFlags flags = blockRef->flags; 50 | 51 | if (flags & BLOCK_HAS_SIGNATURE) { 52 | void *signatureLocation = blockRef->descriptor; 53 | signatureLocation += sizeof(unsigned long int); 54 | signatureLocation += sizeof(unsigned long int); 55 | 56 | if (flags & BLOCK_HAS_COPY_DISPOSE) { 57 | signatureLocation += sizeof(void(*)(void *dst, void *src)); 58 | signatureLocation += sizeof(void (*)(void *src)); 59 | } 60 | 61 | const char *signature = (*(const char **)signatureLocation); 62 | return signature; 63 | } 64 | 65 | return NULL; 66 | } 67 | 68 | static BOOL blockIsCompatibleWithMethodType(id block, const char *methodType){ 69 | 70 | const char *blockType = blockGetType(block); 71 | 72 | NSMethodSignature *blockSignature; 73 | 74 | if (0 == strncmp(blockType, (const char *)"@\"", 2)) { 75 | // Block return type includes class name for id types 76 | // while methodType does not include. 77 | // Stripping out return class name. 78 | char *quotePtr = strchr(blockType+2, '"'); 79 | if (NULL != quotePtr) { 80 | ++quotePtr; 81 | char filteredType[strlen(quotePtr) + 2]; 82 | memset(filteredType, 0, sizeof(filteredType)); 83 | *filteredType = '@'; 84 | strncpy(filteredType + 1, quotePtr, sizeof(filteredType) - 2); 85 | 86 | blockSignature = [NSMethodSignature signatureWithObjCTypes:filteredType]; 87 | }else{ 88 | return NO; 89 | } 90 | }else{ 91 | blockSignature = [NSMethodSignature signatureWithObjCTypes:blockType]; 92 | } 93 | 94 | NSMethodSignature *methodSignature = 95 | [NSMethodSignature signatureWithObjCTypes:methodType]; 96 | 97 | if (!blockSignature || !methodSignature) { 98 | return NO; 99 | } 100 | 101 | if (blockSignature.numberOfArguments != methodSignature.numberOfArguments){ 102 | return NO; 103 | } 104 | 105 | if (strcmp(blockSignature.methodReturnType, methodSignature.methodReturnType) != 0) { 106 | return NO; 107 | } 108 | 109 | for (int i=0; i 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ru.rabovik.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /RSSwizzleTests/RSSwizzleTests-OSX/RSSwizzleTests-OSX-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /RSSwizzleTests/RSSwizzleTests-OSX/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /RSSwizzleTests/RSSwizzleTests-iOS/RSSwizzleTests-iOS-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ru.rabovik.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /RSSwizzleTests/RSSwizzleTests-iOS/RSSwizzleTests-iOS-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #import 10 | #endif 11 | -------------------------------------------------------------------------------- /RSSwizzleTests/RSSwizzleTests-iOS/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /RSSwizzleTests/RSSwizzleTests.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1A1D75C217D8CD58001FDCDC /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A1D75C117D8CD58001FDCDC /* SenTestingKit.framework */; }; 11 | 1A1D75C417D8CD58001FDCDC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A1D75C317D8CD58001FDCDC /* Foundation.framework */; }; 12 | 1A1D75C617D8CD58001FDCDC /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A1D75C517D8CD58001FDCDC /* UIKit.framework */; }; 13 | 1A1D75CC17D8CD58001FDCDC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1A1D75CA17D8CD58001FDCDC /* InfoPlist.strings */; }; 14 | 1A1D75D817D8CD70001FDCDC /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A1D75C117D8CD58001FDCDC /* SenTestingKit.framework */; }; 15 | 1A1D75DE17D8CD70001FDCDC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1A1D75DC17D8CD70001FDCDC /* InfoPlist.strings */; }; 16 | 1A1D75E817D8CDF5001FDCDC /* RSSwizzleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A1D75E717D8CDF5001FDCDC /* RSSwizzleTests.m */; }; 17 | 1A1D75E917D8CDF5001FDCDC /* RSSwizzleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A1D75E717D8CDF5001FDCDC /* RSSwizzleTests.m */; }; 18 | 1A1D75ED17D8CF0D001FDCDC /* RSTestsLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A1D75EC17D8CF0D001FDCDC /* RSTestsLog.m */; }; 19 | 1A1D75EE17D8CF0D001FDCDC /* RSTestsLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A1D75EC17D8CF0D001FDCDC /* RSTestsLog.m */; }; 20 | 1A1D75F117D8D37A001FDCDC /* RSSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A1D75F017D8D37A001FDCDC /* RSSwizzle.m */; }; 21 | 1A1D75F217D8D37A001FDCDC /* RSSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A1D75F017D8D37A001FDCDC /* RSSwizzle.m */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | 1A1D75BE17D8CD58001FDCDC /* RSSwizzleTests-iOS.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "RSSwizzleTests-iOS.octest"; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | 1A1D75C117D8CD58001FDCDC /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; 27 | 1A1D75C317D8CD58001FDCDC /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 28 | 1A1D75C517D8CD58001FDCDC /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; 29 | 1A1D75C917D8CD58001FDCDC /* RSSwizzleTests-iOS-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "RSSwizzleTests-iOS-Info.plist"; sourceTree = ""; }; 30 | 1A1D75CB17D8CD58001FDCDC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 31 | 1A1D75CF17D8CD58001FDCDC /* RSSwizzleTests-iOS-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RSSwizzleTests-iOS-Prefix.pch"; sourceTree = ""; }; 32 | 1A1D75D717D8CD70001FDCDC /* RSSwizzleTests-OSX.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "RSSwizzleTests-OSX.octest"; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | 1A1D75DB17D8CD70001FDCDC /* RSSwizzleTests-OSX-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "RSSwizzleTests-OSX-Info.plist"; sourceTree = ""; }; 34 | 1A1D75DD17D8CD70001FDCDC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 35 | 1A1D75E117D8CD70001FDCDC /* RSSwizzleTests-OSX-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RSSwizzleTests-OSX-Prefix.pch"; sourceTree = ""; }; 36 | 1A1D75E717D8CDF5001FDCDC /* RSSwizzleTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RSSwizzleTests.m; path = RSSwizzleTests/RSSwizzleTests.m; sourceTree = ""; }; 37 | 1A1D75EB17D8CF0D001FDCDC /* RSTestsLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RSTestsLog.h; path = RSSwizzleTests/RSTestsLog.h; sourceTree = ""; }; 38 | 1A1D75EC17D8CF0D001FDCDC /* RSTestsLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RSTestsLog.m; path = RSSwizzleTests/RSTestsLog.m; sourceTree = ""; }; 39 | 1A1D75EF17D8D37A001FDCDC /* RSSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RSSwizzle.h; path = ../RSSwizzle/RSSwizzle.h; sourceTree = ""; }; 40 | 1A1D75F017D8D37A001FDCDC /* RSSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RSSwizzle.m; path = ../RSSwizzle/RSSwizzle.m; sourceTree = ""; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | 1A1D75BB17D8CD58001FDCDC /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | 1A1D75C217D8CD58001FDCDC /* SenTestingKit.framework in Frameworks */, 49 | 1A1D75C617D8CD58001FDCDC /* UIKit.framework in Frameworks */, 50 | 1A1D75C417D8CD58001FDCDC /* Foundation.framework in Frameworks */, 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | 1A1D75D417D8CD70001FDCDC /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | 1A1D75D817D8CD70001FDCDC /* SenTestingKit.framework in Frameworks */, 59 | ); 60 | runOnlyForDeploymentPostprocessing = 0; 61 | }; 62 | /* End PBXFrameworksBuildPhase section */ 63 | 64 | /* Begin PBXGroup section */ 65 | 1A1D75B317D8CD19001FDCDC = { 66 | isa = PBXGroup; 67 | children = ( 68 | 1A1D75EA17D8CE5D001FDCDC /* RSSwizzle */, 69 | 1A1D75E517D8CDA9001FDCDC /* RSSwizzleTests */, 70 | 1A1D75E617D8CDBB001FDCDC /* Supporting Files */, 71 | 1A1D75C017D8CD58001FDCDC /* Frameworks */, 72 | 1A1D75BF17D8CD58001FDCDC /* Products */, 73 | ); 74 | sourceTree = ""; 75 | }; 76 | 1A1D75BF17D8CD58001FDCDC /* Products */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 1A1D75BE17D8CD58001FDCDC /* RSSwizzleTests-iOS.octest */, 80 | 1A1D75D717D8CD70001FDCDC /* RSSwizzleTests-OSX.octest */, 81 | ); 82 | name = Products; 83 | sourceTree = ""; 84 | }; 85 | 1A1D75C017D8CD58001FDCDC /* Frameworks */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 1A1D75C117D8CD58001FDCDC /* SenTestingKit.framework */, 89 | 1A1D75C317D8CD58001FDCDC /* Foundation.framework */, 90 | 1A1D75C517D8CD58001FDCDC /* UIKit.framework */, 91 | ); 92 | name = Frameworks; 93 | sourceTree = ""; 94 | }; 95 | 1A1D75C717D8CD58001FDCDC /* RSSwizzleTests-iOS */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 1A1D75C817D8CD58001FDCDC /* Supporting Files */, 99 | ); 100 | path = "RSSwizzleTests-iOS"; 101 | sourceTree = ""; 102 | }; 103 | 1A1D75C817D8CD58001FDCDC /* Supporting Files */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 1A1D75C917D8CD58001FDCDC /* RSSwizzleTests-iOS-Info.plist */, 107 | 1A1D75CA17D8CD58001FDCDC /* InfoPlist.strings */, 108 | 1A1D75CF17D8CD58001FDCDC /* RSSwizzleTests-iOS-Prefix.pch */, 109 | ); 110 | name = "Supporting Files"; 111 | sourceTree = ""; 112 | }; 113 | 1A1D75D917D8CD70001FDCDC /* RSSwizzleTests-OSX */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 1A1D75DA17D8CD70001FDCDC /* Supporting Files */, 117 | ); 118 | path = "RSSwizzleTests-OSX"; 119 | sourceTree = ""; 120 | }; 121 | 1A1D75DA17D8CD70001FDCDC /* Supporting Files */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 1A1D75DB17D8CD70001FDCDC /* RSSwizzleTests-OSX-Info.plist */, 125 | 1A1D75DC17D8CD70001FDCDC /* InfoPlist.strings */, 126 | 1A1D75E117D8CD70001FDCDC /* RSSwizzleTests-OSX-Prefix.pch */, 127 | ); 128 | name = "Supporting Files"; 129 | sourceTree = ""; 130 | }; 131 | 1A1D75E517D8CDA9001FDCDC /* RSSwizzleTests */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 1A1D75E717D8CDF5001FDCDC /* RSSwizzleTests.m */, 135 | 1A1D75EB17D8CF0D001FDCDC /* RSTestsLog.h */, 136 | 1A1D75EC17D8CF0D001FDCDC /* RSTestsLog.m */, 137 | ); 138 | name = RSSwizzleTests; 139 | sourceTree = ""; 140 | }; 141 | 1A1D75E617D8CDBB001FDCDC /* Supporting Files */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 1A1D75D917D8CD70001FDCDC /* RSSwizzleTests-OSX */, 145 | 1A1D75C717D8CD58001FDCDC /* RSSwizzleTests-iOS */, 146 | ); 147 | name = "Supporting Files"; 148 | sourceTree = ""; 149 | }; 150 | 1A1D75EA17D8CE5D001FDCDC /* RSSwizzle */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 1A1D75EF17D8D37A001FDCDC /* RSSwizzle.h */, 154 | 1A1D75F017D8D37A001FDCDC /* RSSwizzle.m */, 155 | ); 156 | name = RSSwizzle; 157 | sourceTree = ""; 158 | }; 159 | /* End PBXGroup section */ 160 | 161 | /* Begin PBXNativeTarget section */ 162 | 1A1D75BD17D8CD58001FDCDC /* RSSwizzleTests-iOS */ = { 163 | isa = PBXNativeTarget; 164 | buildConfigurationList = 1A1D75D217D8CD58001FDCDC /* Build configuration list for PBXNativeTarget "RSSwizzleTests-iOS" */; 165 | buildPhases = ( 166 | 1A1D75BA17D8CD58001FDCDC /* Sources */, 167 | 1A1D75BB17D8CD58001FDCDC /* Frameworks */, 168 | 1A1D75BC17D8CD58001FDCDC /* Resources */, 169 | ); 170 | buildRules = ( 171 | ); 172 | dependencies = ( 173 | ); 174 | name = "RSSwizzleTests-iOS"; 175 | productName = "RSSwizzleTests-iOS"; 176 | productReference = 1A1D75BE17D8CD58001FDCDC /* RSSwizzleTests-iOS.octest */; 177 | productType = "com.apple.product-type.bundle"; 178 | }; 179 | 1A1D75D617D8CD70001FDCDC /* RSSwizzleTests-OSX */ = { 180 | isa = PBXNativeTarget; 181 | buildConfigurationList = 1A1D75E217D8CD70001FDCDC /* Build configuration list for PBXNativeTarget "RSSwizzleTests-OSX" */; 182 | buildPhases = ( 183 | 1A1D75D317D8CD70001FDCDC /* Sources */, 184 | 1A1D75D417D8CD70001FDCDC /* Frameworks */, 185 | 1A1D75D517D8CD70001FDCDC /* Resources */, 186 | ); 187 | buildRules = ( 188 | ); 189 | dependencies = ( 190 | ); 191 | name = "RSSwizzleTests-OSX"; 192 | productName = "RSSwizzleTests-OSX"; 193 | productReference = 1A1D75D717D8CD70001FDCDC /* RSSwizzleTests-OSX.octest */; 194 | productType = "com.apple.product-type.bundle"; 195 | }; 196 | /* End PBXNativeTarget section */ 197 | 198 | /* Begin PBXProject section */ 199 | 1A1D75B417D8CD19001FDCDC /* Project object */ = { 200 | isa = PBXProject; 201 | attributes = { 202 | LastUpgradeCheck = 0500; 203 | }; 204 | buildConfigurationList = 1A1D75B717D8CD19001FDCDC /* Build configuration list for PBXProject "RSSwizzleTests" */; 205 | compatibilityVersion = "Xcode 3.2"; 206 | developmentRegion = English; 207 | hasScannedForEncodings = 0; 208 | knownRegions = ( 209 | en, 210 | ); 211 | mainGroup = 1A1D75B317D8CD19001FDCDC; 212 | productRefGroup = 1A1D75BF17D8CD58001FDCDC /* Products */; 213 | projectDirPath = ""; 214 | projectRoot = ""; 215 | targets = ( 216 | 1A1D75BD17D8CD58001FDCDC /* RSSwizzleTests-iOS */, 217 | 1A1D75D617D8CD70001FDCDC /* RSSwizzleTests-OSX */, 218 | ); 219 | }; 220 | /* End PBXProject section */ 221 | 222 | /* Begin PBXResourcesBuildPhase section */ 223 | 1A1D75BC17D8CD58001FDCDC /* Resources */ = { 224 | isa = PBXResourcesBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | 1A1D75CC17D8CD58001FDCDC /* InfoPlist.strings in Resources */, 228 | ); 229 | runOnlyForDeploymentPostprocessing = 0; 230 | }; 231 | 1A1D75D517D8CD70001FDCDC /* Resources */ = { 232 | isa = PBXResourcesBuildPhase; 233 | buildActionMask = 2147483647; 234 | files = ( 235 | 1A1D75DE17D8CD70001FDCDC /* InfoPlist.strings in Resources */, 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | /* End PBXResourcesBuildPhase section */ 240 | 241 | /* Begin PBXSourcesBuildPhase section */ 242 | 1A1D75BA17D8CD58001FDCDC /* Sources */ = { 243 | isa = PBXSourcesBuildPhase; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | 1A1D75E817D8CDF5001FDCDC /* RSSwizzleTests.m in Sources */, 247 | 1A1D75ED17D8CF0D001FDCDC /* RSTestsLog.m in Sources */, 248 | 1A1D75F117D8D37A001FDCDC /* RSSwizzle.m in Sources */, 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | }; 252 | 1A1D75D317D8CD70001FDCDC /* Sources */ = { 253 | isa = PBXSourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | 1A1D75E917D8CDF5001FDCDC /* RSSwizzleTests.m in Sources */, 257 | 1A1D75EE17D8CF0D001FDCDC /* RSTestsLog.m in Sources */, 258 | 1A1D75F217D8D37A001FDCDC /* RSSwizzle.m in Sources */, 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | /* End PBXSourcesBuildPhase section */ 263 | 264 | /* Begin PBXVariantGroup section */ 265 | 1A1D75CA17D8CD58001FDCDC /* InfoPlist.strings */ = { 266 | isa = PBXVariantGroup; 267 | children = ( 268 | 1A1D75CB17D8CD58001FDCDC /* en */, 269 | ); 270 | name = InfoPlist.strings; 271 | sourceTree = ""; 272 | }; 273 | 1A1D75DC17D8CD70001FDCDC /* InfoPlist.strings */ = { 274 | isa = PBXVariantGroup; 275 | children = ( 276 | 1A1D75DD17D8CD70001FDCDC /* en */, 277 | ); 278 | name = InfoPlist.strings; 279 | sourceTree = ""; 280 | }; 281 | /* End PBXVariantGroup section */ 282 | 283 | /* Begin XCBuildConfiguration section */ 284 | 1A1D75B817D8CD19001FDCDC /* Debug */ = { 285 | isa = XCBuildConfiguration; 286 | buildSettings = { 287 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 288 | }; 289 | name = Debug; 290 | }; 291 | 1A1D75B917D8CD19001FDCDC /* Release */ = { 292 | isa = XCBuildConfiguration; 293 | buildSettings = { 294 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 295 | }; 296 | name = Release; 297 | }; 298 | 1A1D75D017D8CD58001FDCDC /* Debug */ = { 299 | isa = XCBuildConfiguration; 300 | buildSettings = { 301 | ALWAYS_SEARCH_USER_PATHS = NO; 302 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 303 | CLANG_CXX_LIBRARY = "libc++"; 304 | CLANG_ENABLE_MODULES = YES; 305 | CLANG_ENABLE_OBJC_ARC = YES; 306 | CLANG_WARN_BOOL_CONVERSION = YES; 307 | CLANG_WARN_CONSTANT_CONVERSION = YES; 308 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 309 | CLANG_WARN_EMPTY_BODY = YES; 310 | CLANG_WARN_ENUM_CONVERSION = YES; 311 | CLANG_WARN_INT_CONVERSION = YES; 312 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 313 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 314 | COPY_PHASE_STRIP = NO; 315 | FRAMEWORK_SEARCH_PATHS = ( 316 | "$(SDKROOT)/Developer/Library/Frameworks", 317 | "$(inherited)", 318 | "$(DEVELOPER_FRAMEWORKS_DIR)", 319 | ); 320 | GCC_C_LANGUAGE_STANDARD = gnu99; 321 | GCC_DYNAMIC_NO_PIC = NO; 322 | GCC_OPTIMIZATION_LEVEL = 0; 323 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 324 | GCC_PREFIX_HEADER = "RSSwizzleTests-iOS/RSSwizzleTests-iOS-Prefix.pch"; 325 | GCC_PREPROCESSOR_DEFINITIONS = ( 326 | "DEBUG=1", 327 | "$(inherited)", 328 | ); 329 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 330 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 331 | GCC_WARN_UNDECLARED_SELECTOR = YES; 332 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 333 | GCC_WARN_UNUSED_FUNCTION = YES; 334 | GCC_WARN_UNUSED_VARIABLE = YES; 335 | INFOPLIST_FILE = "RSSwizzleTests-iOS/RSSwizzleTests-iOS-Info.plist"; 336 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 337 | ONLY_ACTIVE_ARCH = YES; 338 | PRODUCT_NAME = "$(TARGET_NAME)"; 339 | SDKROOT = iphoneos; 340 | WRAPPER_EXTENSION = octest; 341 | }; 342 | name = Debug; 343 | }; 344 | 1A1D75D117D8CD58001FDCDC /* Release */ = { 345 | isa = XCBuildConfiguration; 346 | buildSettings = { 347 | ALWAYS_SEARCH_USER_PATHS = NO; 348 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 349 | CLANG_CXX_LIBRARY = "libc++"; 350 | CLANG_ENABLE_MODULES = YES; 351 | CLANG_ENABLE_OBJC_ARC = YES; 352 | CLANG_WARN_BOOL_CONVERSION = YES; 353 | CLANG_WARN_CONSTANT_CONVERSION = YES; 354 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 355 | CLANG_WARN_EMPTY_BODY = YES; 356 | CLANG_WARN_ENUM_CONVERSION = YES; 357 | CLANG_WARN_INT_CONVERSION = YES; 358 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 359 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 360 | COPY_PHASE_STRIP = YES; 361 | ENABLE_NS_ASSERTIONS = YES; 362 | FRAMEWORK_SEARCH_PATHS = ( 363 | "$(SDKROOT)/Developer/Library/Frameworks", 364 | "$(inherited)", 365 | "$(DEVELOPER_FRAMEWORKS_DIR)", 366 | ); 367 | GCC_C_LANGUAGE_STANDARD = gnu99; 368 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 369 | GCC_PREFIX_HEADER = "RSSwizzleTests-iOS/RSSwizzleTests-iOS-Prefix.pch"; 370 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 371 | GCC_WARN_UNDECLARED_SELECTOR = YES; 372 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 373 | GCC_WARN_UNUSED_FUNCTION = YES; 374 | GCC_WARN_UNUSED_VARIABLE = YES; 375 | INFOPLIST_FILE = "RSSwizzleTests-iOS/RSSwizzleTests-iOS-Info.plist"; 376 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 377 | PRODUCT_NAME = "$(TARGET_NAME)"; 378 | SDKROOT = iphoneos; 379 | VALIDATE_PRODUCT = YES; 380 | WRAPPER_EXTENSION = octest; 381 | }; 382 | name = Release; 383 | }; 384 | 1A1D75E317D8CD70001FDCDC /* Debug */ = { 385 | isa = XCBuildConfiguration; 386 | buildSettings = { 387 | ALWAYS_SEARCH_USER_PATHS = NO; 388 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 389 | CLANG_CXX_LIBRARY = "libc++"; 390 | CLANG_ENABLE_MODULES = YES; 391 | CLANG_ENABLE_OBJC_ARC = YES; 392 | CLANG_WARN_BOOL_CONVERSION = YES; 393 | CLANG_WARN_CONSTANT_CONVERSION = YES; 394 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 395 | CLANG_WARN_EMPTY_BODY = YES; 396 | CLANG_WARN_ENUM_CONVERSION = YES; 397 | CLANG_WARN_INT_CONVERSION = YES; 398 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 399 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 400 | COPY_PHASE_STRIP = NO; 401 | FRAMEWORK_SEARCH_PATHS = ( 402 | "$(DEVELOPER_FRAMEWORKS_DIR)", 403 | "$(inherited)", 404 | ); 405 | GCC_C_LANGUAGE_STANDARD = gnu99; 406 | GCC_DYNAMIC_NO_PIC = NO; 407 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 408 | GCC_OPTIMIZATION_LEVEL = 0; 409 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 410 | GCC_PREFIX_HEADER = "RSSwizzleTests-OSX/RSSwizzleTests-OSX-Prefix.pch"; 411 | GCC_PREPROCESSOR_DEFINITIONS = ( 412 | "DEBUG=1", 413 | "$(inherited)", 414 | ); 415 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 416 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 417 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 418 | GCC_WARN_UNDECLARED_SELECTOR = YES; 419 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 420 | GCC_WARN_UNUSED_FUNCTION = YES; 421 | GCC_WARN_UNUSED_VARIABLE = YES; 422 | INFOPLIST_FILE = "RSSwizzleTests-OSX/RSSwizzleTests-OSX-Info.plist"; 423 | MACOSX_DEPLOYMENT_TARGET = 10.7; 424 | ONLY_ACTIVE_ARCH = YES; 425 | PRODUCT_NAME = "$(TARGET_NAME)"; 426 | SDKROOT = macosx; 427 | WRAPPER_EXTENSION = octest; 428 | }; 429 | name = Debug; 430 | }; 431 | 1A1D75E417D8CD70001FDCDC /* Release */ = { 432 | isa = XCBuildConfiguration; 433 | buildSettings = { 434 | ALWAYS_SEARCH_USER_PATHS = NO; 435 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 436 | CLANG_CXX_LIBRARY = "libc++"; 437 | CLANG_ENABLE_MODULES = YES; 438 | CLANG_ENABLE_OBJC_ARC = YES; 439 | CLANG_WARN_BOOL_CONVERSION = YES; 440 | CLANG_WARN_CONSTANT_CONVERSION = YES; 441 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 442 | CLANG_WARN_EMPTY_BODY = YES; 443 | CLANG_WARN_ENUM_CONVERSION = YES; 444 | CLANG_WARN_INT_CONVERSION = YES; 445 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 446 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 447 | COPY_PHASE_STRIP = YES; 448 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 449 | ENABLE_NS_ASSERTIONS = YES; 450 | FRAMEWORK_SEARCH_PATHS = ( 451 | "$(DEVELOPER_FRAMEWORKS_DIR)", 452 | "$(inherited)", 453 | ); 454 | GCC_C_LANGUAGE_STANDARD = gnu99; 455 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 456 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 457 | GCC_PREFIX_HEADER = "RSSwizzleTests-OSX/RSSwizzleTests-OSX-Prefix.pch"; 458 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 459 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 460 | GCC_WARN_UNDECLARED_SELECTOR = YES; 461 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 462 | GCC_WARN_UNUSED_FUNCTION = YES; 463 | GCC_WARN_UNUSED_VARIABLE = YES; 464 | INFOPLIST_FILE = "RSSwizzleTests-OSX/RSSwizzleTests-OSX-Info.plist"; 465 | MACOSX_DEPLOYMENT_TARGET = 10.7; 466 | PRODUCT_NAME = "$(TARGET_NAME)"; 467 | SDKROOT = macosx; 468 | WRAPPER_EXTENSION = octest; 469 | }; 470 | name = Release; 471 | }; 472 | /* End XCBuildConfiguration section */ 473 | 474 | /* Begin XCConfigurationList section */ 475 | 1A1D75B717D8CD19001FDCDC /* Build configuration list for PBXProject "RSSwizzleTests" */ = { 476 | isa = XCConfigurationList; 477 | buildConfigurations = ( 478 | 1A1D75B817D8CD19001FDCDC /* Debug */, 479 | 1A1D75B917D8CD19001FDCDC /* Release */, 480 | ); 481 | defaultConfigurationIsVisible = 0; 482 | defaultConfigurationName = Release; 483 | }; 484 | 1A1D75D217D8CD58001FDCDC /* Build configuration list for PBXNativeTarget "RSSwizzleTests-iOS" */ = { 485 | isa = XCConfigurationList; 486 | buildConfigurations = ( 487 | 1A1D75D017D8CD58001FDCDC /* Debug */, 488 | 1A1D75D117D8CD58001FDCDC /* Release */, 489 | ); 490 | defaultConfigurationIsVisible = 0; 491 | defaultConfigurationName = Release; 492 | }; 493 | 1A1D75E217D8CD70001FDCDC /* Build configuration list for PBXNativeTarget "RSSwizzleTests-OSX" */ = { 494 | isa = XCConfigurationList; 495 | buildConfigurations = ( 496 | 1A1D75E317D8CD70001FDCDC /* Debug */, 497 | 1A1D75E417D8CD70001FDCDC /* Release */, 498 | ); 499 | defaultConfigurationIsVisible = 0; 500 | defaultConfigurationName = Release; 501 | }; 502 | /* End XCConfigurationList section */ 503 | }; 504 | rootObject = 1A1D75B417D8CD19001FDCDC /* Project object */; 505 | } 506 | -------------------------------------------------------------------------------- /RSSwizzleTests/RSSwizzleTests.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RSSwizzleTests/RSSwizzleTests/RSSwizzleTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // RSSwizzleTests.m 3 | // RSSwizzleTests 4 | // 5 | // Created by Yan Rabovik on 05.09.13. 6 | // 7 | // 8 | 9 | #import 10 | #import "RSTestsLog.h" 11 | #import "RSSwizzle.h" 12 | #import 13 | 14 | #pragma mark - HELPER CLASSES - 15 | 16 | @interface RSSwizzleTestClass_A : NSObject @end 17 | @implementation RSSwizzleTestClass_A 18 | -(int)calc:(int)num{ return num; } 19 | -(BOOL)methodReturningBOOL{ return YES; }; 20 | -(void)methodWithArgument:(id)arg{}; 21 | -(void)methodForAlwaysSwizzling{}; 22 | -(void)methodForSwizzlingOncePerClass{}; 23 | -(void)methodForSwizzlingOncePerClassOrSuperClasses{}; 24 | -(NSString *)string{ return @"ABC"; } 25 | +(NSNumber *)sumFloat:(float)floatSummand withDouble:(double)doubleSummand{ 26 | return @(floatSummand + doubleSummand); 27 | } 28 | @end 29 | 30 | @interface RSSwizzleTestClass_B : RSSwizzleTestClass_A @end 31 | @implementation RSSwizzleTestClass_B @end 32 | 33 | @interface RSSwizzleTestClass_C : RSSwizzleTestClass_B @end 34 | @implementation RSSwizzleTestClass_C 35 | -(void)dealloc{ RSTestsLog(@"C-"); }; 36 | -(int)calc:(int)num{ return [super calc:num] * 3; } 37 | @end 38 | 39 | @interface RSSwizzleTestClass_D : RSSwizzleTestClass_C @end 40 | @implementation RSSwizzleTestClass_D @end 41 | 42 | @interface RSSwizzleTestClass_D2 : RSSwizzleTestClass_C @end 43 | @implementation RSSwizzleTestClass_D2 @end 44 | 45 | #pragma mark - HELPER FUNCTIONS - 46 | 47 | static void swizzleVoidMethod(Class classToSwizzle, 48 | SEL selector, 49 | dispatch_block_t blockBefore, 50 | RSSwizzleMode mode, 51 | const void *key) 52 | { 53 | RSSwizzleInstanceMethod(classToSwizzle, 54 | selector, 55 | RSSWReturnType(void), 56 | RSSWArguments(), 57 | RSSWReplacement( 58 | { 59 | blockBefore(); 60 | RSSWCallOriginal(); 61 | }), mode, key); 62 | } 63 | 64 | static void swizzleDealloc(Class classToSwizzle, dispatch_block_t blockBefore){ 65 | SEL selector = NSSelectorFromString(@"dealloc"); 66 | swizzleVoidMethod(classToSwizzle, selector, blockBefore, RSSwizzleModeAlways, NULL); 67 | } 68 | 69 | static void swizzleNumber(Class classToSwizzle, int(^transformationBlock)(int)){ 70 | RSSwizzleInstanceMethod(classToSwizzle, 71 | @selector(calc:), 72 | RSSWReturnType(int), 73 | RSSWArguments(int num), 74 | RSSWReplacement( 75 | { 76 | int res = RSSWCallOriginal(num); 77 | return transformationBlock(res); 78 | }), RSSwizzleModeAlways, NULL); 79 | } 80 | 81 | #pragma mark - TESTS - 82 | 83 | @interface RSSwizzleTests : SenTestCase @end 84 | 85 | @implementation RSSwizzleTests 86 | 87 | #pragma mark - Setup 88 | 89 | +(void)setUp{ 90 | [self swizzleDeallocs]; 91 | [self swizzleCalc]; 92 | } 93 | 94 | -(void)setUp{ 95 | [super setUp]; 96 | CLEAR_LOG(); 97 | } 98 | 99 | #pragma mark - Dealloc Swizzling 100 | 101 | +(void)swizzleDeallocs{ 102 | // 1) Swizzling a class that does not implement the method... 103 | swizzleDealloc([RSSwizzleTestClass_D class], ^{ 104 | RSTestsLog(@"d-"); 105 | }); 106 | // ...should not break swizzling of its superclass. 107 | swizzleDealloc([RSSwizzleTestClass_C class], ^{ 108 | RSTestsLog(@"c-"); 109 | }); 110 | // 2) Swizzling a class that does not implement the method 111 | // should not affect classes with the same superclass. 112 | swizzleDealloc([RSSwizzleTestClass_D2 class], ^{ 113 | RSTestsLog(@"d2-"); 114 | }); 115 | 116 | // 3) We should be able to swizzle classes several times... 117 | swizzleDealloc([RSSwizzleTestClass_D class], ^{ 118 | RSTestsLog(@"d'-"); 119 | }); 120 | // ...and nothing should be breaked up. 121 | swizzleDealloc([RSSwizzleTestClass_C class], ^{ 122 | RSTestsLog(@"c'-"); 123 | }); 124 | 125 | // 4) Swizzling a class inherited from NSObject and does not 126 | // implementing the method. 127 | swizzleDealloc([RSSwizzleTestClass_A class], ^{ 128 | RSTestsLog(@"a"); 129 | }); 130 | } 131 | 132 | -(void)testDeallocSwizzling 133 | { 134 | @autoreleasepool { 135 | id object = [RSSwizzleTestClass_D new]; 136 | object = nil; 137 | } 138 | ASSERT_LOG_IS(@"d'-d-c'-c-C-a"); 139 | } 140 | 141 | #pragma mark - Calc: Swizzling 142 | 143 | +(void)swizzleCalc{ 144 | 145 | swizzleNumber([RSSwizzleTestClass_C class], ^int(int num){ 146 | return num + 17; 147 | }); 148 | 149 | swizzleNumber([RSSwizzleTestClass_D class], ^int(int num){ 150 | return num * 11; 151 | }); 152 | swizzleNumber([RSSwizzleTestClass_C class], ^int(int num){ 153 | return num * 5; 154 | }); 155 | swizzleNumber([RSSwizzleTestClass_D class], ^int(int num){ 156 | return num - 20; 157 | }); 158 | 159 | swizzleNumber([RSSwizzleTestClass_A class], ^int(int num){ 160 | return num * -1; 161 | }); 162 | } 163 | 164 | -(void)testCalcSwizzling 165 | { 166 | RSSwizzleTestClass_D *object = [RSSwizzleTestClass_D new]; 167 | int res = [object calc:2]; 168 | STAssertTrue(res == ((2 * (-1) * 3) + 17) * 5 * 11 - 20, @"%d",res); 169 | } 170 | 171 | #pragma mark - String Swizzling 172 | -(void)testStringSwizzling{ 173 | SEL selector = @selector(string); 174 | RSSwizzleTestClass_A *a = [RSSwizzleTestClass_A new]; 175 | 176 | RSSwizzleInstanceMethod([a class], 177 | selector, 178 | RSSWReturnType(NSString *), 179 | RSSWArguments(), 180 | RSSWReplacement( 181 | { 182 | NSString *res = RSSWCallOriginal(); 183 | return [res stringByAppendingString:@"DEF"]; 184 | }), RSSwizzleModeAlways, NULL); 185 | 186 | STAssertTrue([[a string] isEqualToString:@"ABCDEF"], nil); 187 | } 188 | 189 | #pragma mark - Class Swizzling 190 | -(void)testClassSwizzling{ 191 | RSSwizzleClassMethod([RSSwizzleTestClass_B class], 192 | @selector(sumFloat:withDouble:), 193 | RSSWReturnType(NSNumber *), 194 | RSSWArguments(float floatSummand, double doubleSummand), 195 | RSSWReplacement( 196 | { 197 | NSNumber *result = RSSWCallOriginal(floatSummand, doubleSummand); 198 | return @([result doubleValue]* 2.); 199 | })); 200 | 201 | STAssertEqualObjects(@(2.), [RSSwizzleTestClass_A sumFloat:0.5 withDouble:1.5 ], nil); 202 | STAssertEqualObjects(@(4.), [RSSwizzleTestClass_B sumFloat:0.5 withDouble:1.5 ], nil); 203 | STAssertEqualObjects(@(4.), [RSSwizzleTestClass_C sumFloat:0.5 withDouble:1.5 ], nil); 204 | } 205 | 206 | #pragma mark - Test Assertions 207 | #if !defined(NS_BLOCK_ASSERTIONS) 208 | -(void)testThrowsOnSwizzlingNonexistentMethod{ 209 | SEL selector = NSSelectorFromString(@"nonexistent"); 210 | RSSwizzleImpFactoryBlock factoryBlock = ^id(RSSwizzleInfo *swizzleInfo){ 211 | return ^(__unsafe_unretained id self){ 212 | void (*originalIMP)(__unsafe_unretained id, SEL); 213 | originalIMP = (__typeof(originalIMP))[swizzleInfo getOriginalImplementation]; 214 | originalIMP(self,selector); 215 | }; 216 | }; 217 | STAssertThrows([RSSwizzle 218 | swizzleInstanceMethod:selector 219 | inClass:[RSSwizzleTestClass_A class] 220 | newImpFactory:factoryBlock 221 | mode:RSSwizzleModeAlways 222 | key:NULL], nil); 223 | } 224 | 225 | -(void)testThrowsOnSwizzlingWithIncorrectImpType{ 226 | // Different return types 227 | RSSwizzleImpFactoryBlock voidNoArgFactory = 228 | ^id(RSSwizzleInfo *swizzleInfo) 229 | { 230 | return ^(__unsafe_unretained id self){}; 231 | }; 232 | STAssertThrows([RSSwizzle 233 | swizzleInstanceMethod:@selector(methodReturningBOOL) 234 | inClass:[RSSwizzleTestClass_A class] 235 | newImpFactory:voidNoArgFactory 236 | mode:RSSwizzleModeAlways 237 | key:NULL], nil); 238 | // Different arguments count 239 | STAssertThrows([RSSwizzle 240 | swizzleInstanceMethod:@selector(methodWithArgument:) 241 | inClass:[RSSwizzleTestClass_A class] 242 | newImpFactory:voidNoArgFactory 243 | mode:RSSwizzleModeAlways 244 | key:NULL], nil); 245 | // Different arguments type 246 | RSSwizzleImpFactoryBlock voidIntArgFactory = 247 | ^id(RSSwizzleInfo *swizzleInfo) 248 | { 249 | return ^int(__unsafe_unretained id self){ return 0; }; 250 | }; 251 | STAssertThrows([RSSwizzle 252 | swizzleInstanceMethod:@selector(methodWithArgument:) 253 | inClass:[RSSwizzleTestClass_A class] 254 | newImpFactory:voidIntArgFactory 255 | mode:RSSwizzleModeAlways 256 | key:NULL], nil); 257 | } 258 | 259 | -(void)testThrowsOnPassingIncorrectImpFactory{ 260 | STAssertThrows([RSSwizzle 261 | swizzleInstanceMethod:@selector(methodWithArgument:) 262 | inClass:[RSSwizzleTestClass_A class] 263 | newImpFactory:^id(id x){ return nil; } 264 | mode:RSSwizzleModeAlways 265 | key:NULL], nil); 266 | } 267 | #endif 268 | 269 | #pragma mark - Mode tests 270 | 271 | -(void)testAlwaysSwizzlingMode{ 272 | for (int i = 3; i > 0; --i) { 273 | swizzleVoidMethod([RSSwizzleTestClass_A class], 274 | @selector(methodForAlwaysSwizzling), ^{ 275 | RSTestsLog(@"A"); 276 | }, 277 | RSSwizzleModeAlways, 278 | NULL); 279 | swizzleVoidMethod([RSSwizzleTestClass_B class], 280 | @selector(methodForAlwaysSwizzling), ^{ 281 | RSTestsLog(@"B"); 282 | }, 283 | RSSwizzleModeAlways, 284 | NULL); 285 | } 286 | 287 | RSSwizzleTestClass_B *object = [RSSwizzleTestClass_B new]; 288 | [object methodForAlwaysSwizzling]; 289 | ASSERT_LOG_IS(@"BBBAAA"); 290 | } 291 | 292 | -(void)testSwizzleOncePerClassMode{ 293 | static void *key = &key; 294 | for (int i = 3; i > 0; --i) { 295 | swizzleVoidMethod([RSSwizzleTestClass_A class], 296 | @selector(methodForSwizzlingOncePerClass), ^{ 297 | RSTestsLog(@"A"); 298 | }, 299 | RSSwizzleModeOncePerClass, 300 | key); 301 | swizzleVoidMethod([RSSwizzleTestClass_B class], 302 | @selector(methodForSwizzlingOncePerClass), ^{ 303 | RSTestsLog(@"B"); 304 | }, 305 | RSSwizzleModeOncePerClass, 306 | key); 307 | } 308 | RSSwizzleTestClass_B *object = [RSSwizzleTestClass_B new]; 309 | [object methodForSwizzlingOncePerClass]; 310 | ASSERT_LOG_IS(@"BA"); 311 | } 312 | 313 | -(void)testSwizzleOncePerClassOrSuperClassesMode{ 314 | static void *key = &key; 315 | for (int i = 3; i > 0; --i) { 316 | swizzleVoidMethod([RSSwizzleTestClass_A class], 317 | @selector(methodForSwizzlingOncePerClassOrSuperClasses), ^{ 318 | RSTestsLog(@"A"); 319 | }, 320 | RSSwizzleModeOncePerClassAndSuperclasses, 321 | key); 322 | swizzleVoidMethod([RSSwizzleTestClass_B class], 323 | @selector(methodForSwizzlingOncePerClassOrSuperClasses), ^{ 324 | RSTestsLog(@"B"); 325 | }, 326 | RSSwizzleModeOncePerClassAndSuperclasses, 327 | key); 328 | } 329 | RSSwizzleTestClass_B *object = [RSSwizzleTestClass_B new]; 330 | [object methodForSwizzlingOncePerClassOrSuperClasses]; 331 | ASSERT_LOG_IS(@"A"); 332 | } 333 | 334 | @end 335 | -------------------------------------------------------------------------------- /RSSwizzleTests/RSSwizzleTests/RSTestsLog.h: -------------------------------------------------------------------------------- 1 | @interface RSTestsLog : NSObject 2 | +(void)log:(NSString *)string; 3 | +(void)clear; 4 | +(BOOL)is:(NSString *)compareString; 5 | +(NSString *)logString; 6 | @end 7 | 8 | #define ASSERT_LOG_IS(STRING) STAssertTrue([RSTestsLog is:STRING], @"LOG IS @\"%@\" INSTEAD",[RSTestsLog logString]) 9 | #define CLEAR_LOG() ([RSTestsLog clear]) 10 | #define RSTestsLog(STRING) [RSTestsLog log:STRING] -------------------------------------------------------------------------------- /RSSwizzleTests/RSSwizzleTests/RSTestsLog.m: -------------------------------------------------------------------------------- 1 | #import "RSTestsLog.h" 2 | #import 3 | 4 | @implementation RSTestsLog 5 | 6 | static NSMutableString *_logString = nil; 7 | +(void)log:(NSString *)string{ 8 | if (!_logString) { 9 | _logString = [NSMutableString new]; 10 | } 11 | [_logString appendString:string]; 12 | NSLog(@"%@",string); 13 | } 14 | +(void)clear{ 15 | _logString = [NSMutableString new]; 16 | } 17 | +(BOOL)is:(NSString *)compareString{ 18 | return [compareString isEqualToString:_logString]; 19 | } 20 | +(NSString *)logString{ 21 | return _logString; 22 | } 23 | 24 | @end 25 | 26 | 27 | @interface RSTestsLogTests : SenTestCase @end 28 | 29 | @implementation RSTestsLogTests 30 | 31 | - (void)testLog{ 32 | [RSTestsLog clear]; 33 | RSTestsLog(@"A"); 34 | RSTestsLog(@"B"); 35 | RSTestsLog(@"C"); 36 | STAssertTrue([[RSTestsLog logString] isEqualToString:@"ABC"], @"%@",[RSTestsLog logString]); 37 | } 38 | 39 | -(void)testAssertLogIs{ 40 | [RSTestsLog clear]; 41 | RSTestsLog(@"A"); 42 | RSTestsLog(@"C"); 43 | ASSERT_LOG_IS(@"AC"); 44 | } 45 | 46 | @end 47 | --------------------------------------------------------------------------------