415 lines
17 KiB
C
415 lines
17 KiB
C
|
|
#import <Foundation/Foundation.h>
|
||
|
|
|
||
|
|
#pragma mark - Macros Based API
|
||
|
|
|
||
|
|
/**
|
||
|
|
* A macro for wrapping the return type of the swizzled method.
|
||
|
|
*/
|
||
|
|
#define SentrySWReturnType(type) type
|
||
|
|
|
||
|
|
/**
|
||
|
|
* A macro for wrapping arguments of the swizzled method.
|
||
|
|
*/
|
||
|
|
#define SentrySWArguments(arguments...) _SentrySWArguments(arguments)
|
||
|
|
|
||
|
|
/**
|
||
|
|
* A macro for wrapping the replacement code for the swizzled method.
|
||
|
|
*/
|
||
|
|
#define SentrySWReplacement(code...) code
|
||
|
|
|
||
|
|
/**
|
||
|
|
* A macro for casting and calling original implementation.
|
||
|
|
* @note May be used only in @c SentrySwizzleInstanceMethod or @c SentrySwizzleClassMethod macros.
|
||
|
|
*/
|
||
|
|
#define SentrySWCallOriginal(arguments...) _SentrySWCallOriginal(arguments)
|
||
|
|
|
||
|
|
#pragma mark └ Swizzle Instance Method
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Swizzles the instance method of the class with the new implementation.
|
||
|
|
*
|
||
|
|
* Example for swizzling @c -(int)calculate:(int)number; method:
|
||
|
|
*
|
||
|
|
* @code
|
||
|
|
*
|
||
|
|
* SentrySwizzleInstanceMethod(classToSwizzle,
|
||
|
|
* @selector(calculate:),
|
||
|
|
* SentrySWReturnType(int),
|
||
|
|
* SentrySWArguments(int number),
|
||
|
|
* SentrySWReplacement(
|
||
|
|
* {
|
||
|
|
* // Calling original implementation.
|
||
|
|
* int res = SentrySWCallOriginal(number);
|
||
|
|
* // Returning modified return value.
|
||
|
|
* return res + 1;
|
||
|
|
* }), 0, NULL);
|
||
|
|
*
|
||
|
|
* @endcode
|
||
|
|
*
|
||
|
|
* Swizzling frequently goes along with checking whether this particular class (or
|
||
|
|
* one of its superclasses) has been already swizzled. Here the
|
||
|
|
* @c SentrySwizzleMode and @c key parameters can help.
|
||
|
|
* @see @code +[SentrySwizzle swizzleInstanceMethod:inClass:newImpFactory:mode:key:] @endcode
|
||
|
|
*
|
||
|
|
* Swizzling is fully thread-safe.
|
||
|
|
*
|
||
|
|
* @param classToSwizzle The class with the method that should be swizzled.
|
||
|
|
*
|
||
|
|
* @param selector Selector of the method that should be swizzled.
|
||
|
|
*
|
||
|
|
* @param SentrySWReturnType The return type of the swizzled method wrapped in the
|
||
|
|
* @c SentrySWReturnType macro.
|
||
|
|
*
|
||
|
|
* @param SentrySWArguments The arguments of the swizzled method wrapped in the
|
||
|
|
* @c SentrySWArguments macro.
|
||
|
|
*
|
||
|
|
* @param SentrySWReplacement The code of the new implementation of the swizzled
|
||
|
|
* method wrapped in the @c SentrySWReplacement macro.
|
||
|
|
*
|
||
|
|
* @param SentrySwizzleMode The mode is used in combination with the key to
|
||
|
|
* indicate whether the swizzling should be done for the given class. You can pass
|
||
|
|
* @c 0 for @c SentrySwizzleModeAlways.
|
||
|
|
*
|
||
|
|
* @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 @c NULL if the mode is
|
||
|
|
* @c SentrySwizzleModeAlways.
|
||
|
|
*
|
||
|
|
* @return @c YES if successfully swizzled and @c NO if swizzling has been already done
|
||
|
|
* for given key and class (or one of superclasses, depends on the mode).
|
||
|
|
*/
|
||
|
|
#define SentrySwizzleInstanceMethod(classToSwizzle, selector, SentrySWReturnType, \
|
||
|
|
SentrySWArguments, SentrySWReplacement, SentrySwizzleMode, key) \
|
||
|
|
_SentrySwizzleInstanceMethod(classToSwizzle, selector, SentrySWReturnType, \
|
||
|
|
_SentrySWWrapArg(SentrySWArguments), _SentrySWWrapArg(SentrySWReplacement), \
|
||
|
|
SentrySwizzleMode, key)
|
||
|
|
|
||
|
|
#pragma mark └ Swizzle Class Method
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Swizzles the class method of the class with the new implementation.
|
||
|
|
*
|
||
|
|
* Example for swizzling @c +(int)calculate:(int)number; method:
|
||
|
|
*
|
||
|
|
* @code
|
||
|
|
*
|
||
|
|
* SentrySwizzleClassMethod(classToSwizzle,
|
||
|
|
* @selector(calculate:),
|
||
|
|
* SentrySWReturnType(int),
|
||
|
|
* SentrySWArguments(int number),
|
||
|
|
* SentrySWReplacement(
|
||
|
|
* {
|
||
|
|
* // Calling original implementation.
|
||
|
|
* int res = SentrySWCallOriginal(number);
|
||
|
|
* // Returning modified return value.
|
||
|
|
* return res + 1;
|
||
|
|
* }));
|
||
|
|
*
|
||
|
|
* @endcode
|
||
|
|
*
|
||
|
|
* Swizzling is fully thread-safe.
|
||
|
|
*
|
||
|
|
* @param classToSwizzle The class with the method that should be swizzled.
|
||
|
|
*
|
||
|
|
* @param selector Selector of the method that should be swizzled.
|
||
|
|
*
|
||
|
|
* @param SentrySWReturnType The return type of the swizzled method wrapped in the
|
||
|
|
* SentrySWReturnType macro.
|
||
|
|
*
|
||
|
|
* @param SentrySWArguments The arguments of the swizzled method wrapped in the
|
||
|
|
* SentrySWArguments macro.
|
||
|
|
*
|
||
|
|
* @param SentrySWReplacement The code of the new implementation of the swizzled
|
||
|
|
* method wrapped in the SentrySWReplacement macro.
|
||
|
|
*/
|
||
|
|
#define SentrySwizzleClassMethod( \
|
||
|
|
classToSwizzle, selector, SentrySWReturnType, SentrySWArguments, SentrySWReplacement) \
|
||
|
|
_SentrySwizzleClassMethod(classToSwizzle, selector, SentrySWReturnType, \
|
||
|
|
_SentrySWWrapArg(SentrySWArguments), _SentrySWWrapArg(SentrySWReplacement))
|
||
|
|
|
||
|
|
#pragma mark - Main API
|
||
|
|
|
||
|
|
/**
|
||
|
|
* A function pointer to the original implementation of the swizzled method.
|
||
|
|
*/
|
||
|
|
typedef void (*SentrySwizzleOriginalIMP)(void /* id, SEL, ... */);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @c SentrySwizzleInfo is used in the new implementation block to get and call
|
||
|
|
* original implementation of the swizzled method.
|
||
|
|
*/
|
||
|
|
@interface SentrySwizzleInfo : NSObject
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns the original implementation of the swizzled method.
|
||
|
|
*
|
||
|
|
* 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.
|
||
|
|
*
|
||
|
|
* @note You must always cast returned implementation to the appropriate function
|
||
|
|
* pointer when calling.
|
||
|
|
*
|
||
|
|
* @return A function pointer to the original implementation of the swizzled
|
||
|
|
* method.
|
||
|
|
*/
|
||
|
|
- (SentrySwizzleOriginalIMP)getOriginalImplementation;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The selector of the swizzled method.
|
||
|
|
*/
|
||
|
|
@property (nonatomic, readonly) SEL selector;
|
||
|
|
|
||
|
|
#if defined(TEST) || defined(TESTCI)
|
||
|
|
/**
|
||
|
|
* A flag to check whether the original implementation was called.
|
||
|
|
*/
|
||
|
|
@property (nonatomic) BOOL originalCalled;
|
||
|
|
#endif // defined(TEST) || defined(TESTCI) || defined(DEBUG)
|
||
|
|
|
||
|
|
@end
|
||
|
|
|
||
|
|
/**
|
||
|
|
* A factory block returning the block for the new implementation of the swizzled
|
||
|
|
* method.
|
||
|
|
*
|
||
|
|
* You must always obtain original implementation with @c swizzleInfo and call it
|
||
|
|
* from the new implementation.
|
||
|
|
*
|
||
|
|
* @param swizzleInfo An info used to get and call the original implementation of
|
||
|
|
* the swizzled method.
|
||
|
|
*
|
||
|
|
* @return A block providing an implementation for the swizzled method. The selector is not
|
||
|
|
* available as a parameter to this block. Its signature should be: @code method_return_type ^(id
|
||
|
|
* self, method_args...) @endcode
|
||
|
|
*/
|
||
|
|
typedef id (^SentrySwizzleImpFactoryBlock)(SentrySwizzleInfo *swizzleInfo);
|
||
|
|
|
||
|
|
typedef NS_ENUM(NSUInteger, SentrySwizzleMode) {
|
||
|
|
/**
|
||
|
|
* @c SentrySwizzle always does swizzling.
|
||
|
|
*/
|
||
|
|
SentrySwizzleModeAlways = 0,
|
||
|
|
/**
|
||
|
|
* @c SentrySwizzle does not do swizzling if the same class has been swizzled
|
||
|
|
* earlier with the same key.
|
||
|
|
*/
|
||
|
|
SentrySwizzleModeOncePerClass = 1,
|
||
|
|
/**
|
||
|
|
* @c SentrySwizzle does not do swizzling if the same class or one of its
|
||
|
|
* superclasses have been swizzled earlier with the same key.
|
||
|
|
* @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.
|
||
|
|
*/
|
||
|
|
SentrySwizzleModeOncePerClassAndSuperclasses = 2
|
||
|
|
};
|
||
|
|
|
||
|
|
@interface SentrySwizzle : NSObject
|
||
|
|
|
||
|
|
#pragma mark └ Swizzle Instance Method
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Swizzles the instance method of the class with the new implementation.
|
||
|
|
*
|
||
|
|
* 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.
|
||
|
|
*
|
||
|
|
* 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.
|
||
|
|
*
|
||
|
|
* Example for swizzling @c -(int)calculate:(int)number; method:
|
||
|
|
*
|
||
|
|
* @code
|
||
|
|
*
|
||
|
|
* SEL selector = @selector(calculate:);
|
||
|
|
* [SentrySwizzle
|
||
|
|
* swizzleInstanceMethod:selector
|
||
|
|
* inClass:classToSwizzle
|
||
|
|
* newImpFactory:^id(SentrySwizzleInfo *swizzleInfo) {
|
||
|
|
* // This block will be used as the new implementation.
|
||
|
|
* return ^int(__unsafe_unretained id self, int num){
|
||
|
|
* // You MUST always cast implementation to the correct function
|
||
|
|
* pointer. int (*originalIMP)(__unsafe_unretained id, SEL, int); originalIMP =
|
||
|
|
* (__typeof(originalIMP))[swizzleInfo getOriginalImplementation];
|
||
|
|
* // Calling original implementation.
|
||
|
|
* int res = originalIMP(self,selector,num);
|
||
|
|
* // Returning modified return value.
|
||
|
|
* return res + 1;
|
||
|
|
* };
|
||
|
|
* }
|
||
|
|
* mode:SentrySwizzleModeAlways
|
||
|
|
* key:NULL];
|
||
|
|
*
|
||
|
|
* @endcode
|
||
|
|
*
|
||
|
|
* Swizzling frequently goes along with checking whether this particular class (or
|
||
|
|
* one of its superclasses) has been already swizzled. Here the @c mode and @c key
|
||
|
|
* parameters can help.
|
||
|
|
*
|
||
|
|
* Here is an example of swizzling @c -(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.
|
||
|
|
*
|
||
|
|
* @code
|
||
|
|
*
|
||
|
|
* static const void *key = &key;
|
||
|
|
* SEL selector = NSSelectorFromString(@"dealloc");
|
||
|
|
* [SentrySwizzle
|
||
|
|
* swizzleInstanceMethod:selector
|
||
|
|
* inClass:classToSwizzle
|
||
|
|
* newImpFactory:^id(SentrySwizzleInfo *swizzleInfo) {
|
||
|
|
* return ^void(__unsafe_unretained id self){
|
||
|
|
* NSLog(@"Deallocating %@.",self);
|
||
|
|
*
|
||
|
|
* void (*originalIMP)(__unsafe_unretained id, SEL);
|
||
|
|
* originalIMP = (__typeof(originalIMP))[swizzleInfo
|
||
|
|
* getOriginalImplementation]; originalIMP(self,selector);
|
||
|
|
* };
|
||
|
|
* }
|
||
|
|
* mode:SentrySwizzleModeOncePerClassAndSuperclasses
|
||
|
|
* key:key];
|
||
|
|
*
|
||
|
|
* @endcode
|
||
|
|
*
|
||
|
|
* Swizzling is fully thread-safe.
|
||
|
|
*
|
||
|
|
* @param selector Selector of the method that should be swizzled.
|
||
|
|
*
|
||
|
|
* @param classToSwizzle The class with the method that should be swizzled.
|
||
|
|
*
|
||
|
|
* @param factoryBlock The factory block returning the block for the new
|
||
|
|
* implementation of the swizzled method.
|
||
|
|
*
|
||
|
|
* @param mode The mode is used in combination with the key to indicate whether
|
||
|
|
* the swizzling should be done for the given class.
|
||
|
|
*
|
||
|
|
* @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 @c NULL if the mode is
|
||
|
|
* SentrySwizzleModeAlways.
|
||
|
|
*
|
||
|
|
* @return @c YES if successfully swizzled and @c NO if swizzling has been already done
|
||
|
|
* for given key and class (or one of superclasses, depends on the mode).
|
||
|
|
*/
|
||
|
|
+ (BOOL)swizzleInstanceMethod:(SEL)selector
|
||
|
|
inClass:(Class)classToSwizzle
|
||
|
|
newImpFactory:(SentrySwizzleImpFactoryBlock)factoryBlock
|
||
|
|
mode:(SentrySwizzleMode)mode
|
||
|
|
key:(const void *)key;
|
||
|
|
|
||
|
|
#pragma mark └ Swizzle Class method
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Swizzles the class method of the class with the new implementation.
|
||
|
|
*
|
||
|
|
* 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.
|
||
|
|
*
|
||
|
|
* You should pass a factory block that returns the block for the new
|
||
|
|
* implementation of the swizzled method. And use @c swizzleInfo argument to retrieve
|
||
|
|
* and call original implementation.
|
||
|
|
*
|
||
|
|
* Example for swizzling @c +(int)calculate:(int)number; method:
|
||
|
|
*
|
||
|
|
* @code
|
||
|
|
*
|
||
|
|
* SEL selector = @selector(calculate:);
|
||
|
|
* [SentrySwizzle
|
||
|
|
* swizzleClassMethod:selector
|
||
|
|
* inClass:classToSwizzle
|
||
|
|
* newImpFactory:^id(SentrySwizzleInfo *swizzleInfo) {
|
||
|
|
* // This block will be used as the new implementation.
|
||
|
|
* return ^int(__unsafe_unretained id self, int num){
|
||
|
|
* // You MUST always cast implementation to the correct function
|
||
|
|
* pointer. int (*originalIMP)(__unsafe_unretained id, SEL, int); originalIMP =
|
||
|
|
* (__typeof(originalIMP))[swizzleInfo getOriginalImplementation];
|
||
|
|
* // Calling original implementation.
|
||
|
|
* int res = originalIMP(self,selector,num);
|
||
|
|
* // Returning modified return value.
|
||
|
|
* return res + 1;
|
||
|
|
* };
|
||
|
|
* }];
|
||
|
|
*
|
||
|
|
* @endcode
|
||
|
|
*
|
||
|
|
* Swizzling is fully thread-safe.
|
||
|
|
*
|
||
|
|
* @param selector Selector of the method that should be swizzled.
|
||
|
|
*
|
||
|
|
* @param classToSwizzle The class with the method that should be swizzled.
|
||
|
|
*
|
||
|
|
* @param factoryBlock The factory block returning the block for the new
|
||
|
|
* implementation of the swizzled method.
|
||
|
|
*/
|
||
|
|
+ (void)swizzleClassMethod:(SEL)selector
|
||
|
|
inClass:(Class)classToSwizzle
|
||
|
|
newImpFactory:(SentrySwizzleImpFactoryBlock)factoryBlock;
|
||
|
|
|
||
|
|
@end
|
||
|
|
|
||
|
|
#pragma mark - Implementation details
|
||
|
|
// Do not write code that depends on anything below this line.
|
||
|
|
|
||
|
|
// Wrapping arguments to pass them as a single argument to another macro.
|
||
|
|
#define _SentrySWWrapArg(args...) args
|
||
|
|
|
||
|
|
#define _SentrySWDel2Arg(a1, a2, args...) a1, ##args
|
||
|
|
#define _SentrySWDel3Arg(a1, a2, a3, args...) a1, a2, ##args
|
||
|
|
|
||
|
|
// To prevent comma issues if there are no arguments we add one dummy argument
|
||
|
|
// and remove it later.
|
||
|
|
#define _SentrySWArguments(arguments...) DEL, ##arguments
|
||
|
|
|
||
|
|
#if defined(TEST) || defined(TESTCI)
|
||
|
|
# define _SentrySWReplacement(code...) \
|
||
|
|
@try { \
|
||
|
|
code \
|
||
|
|
} @finally { \
|
||
|
|
if (!swizzleInfo.originalCalled) \
|
||
|
|
@throw([NSException exceptionWithName:@"SwizzlingError" \
|
||
|
|
reason:@"Original method not called" \
|
||
|
|
userInfo:nil]); \
|
||
|
|
}
|
||
|
|
#else
|
||
|
|
# define _SentrySWReplacement(code...) code
|
||
|
|
#endif // defined(TEST) || defined(TESTCI) || defined(DEBUG)
|
||
|
|
|
||
|
|
#define _SentrySwizzleInstanceMethod(classToSwizzle, selector, SentrySWReturnType, \
|
||
|
|
SentrySWArguments, SentrySWReplacement, SentrySwizzleMode, KEY) \
|
||
|
|
[SentrySwizzle \
|
||
|
|
swizzleInstanceMethod:selector \
|
||
|
|
inClass:[classToSwizzle class] \
|
||
|
|
newImpFactory:^id(SentrySwizzleInfo *swizzleInfo) { \
|
||
|
|
SentrySWReturnType (*originalImplementation_)( \
|
||
|
|
_SentrySWDel3Arg(__unsafe_unretained id, SEL, SentrySWArguments)); \
|
||
|
|
SEL selector_ = selector; \
|
||
|
|
return ^SentrySWReturnType(_SentrySWDel2Arg(__unsafe_unretained id self, \
|
||
|
|
SentrySWArguments)) { _SentrySWReplacement(SentrySWReplacement) }; \
|
||
|
|
} \
|
||
|
|
mode:SentrySwizzleMode \
|
||
|
|
key:KEY];
|
||
|
|
|
||
|
|
#define _SentrySwizzleClassMethod( \
|
||
|
|
classToSwizzle, selector, SentrySWReturnType, SentrySWArguments, SentrySWReplacement) \
|
||
|
|
[SentrySwizzle \
|
||
|
|
swizzleClassMethod:selector \
|
||
|
|
inClass:[classToSwizzle class] \
|
||
|
|
newImpFactory:^id(SentrySwizzleInfo *swizzleInfo) { \
|
||
|
|
SentrySWReturnType (*originalImplementation_)( \
|
||
|
|
_SentrySWDel3Arg(__unsafe_unretained id, SEL, SentrySWArguments)); \
|
||
|
|
SEL selector_ = selector; \
|
||
|
|
return ^SentrySWReturnType(_SentrySWDel2Arg(__unsafe_unretained id self, \
|
||
|
|
SentrySWArguments)) { _SentrySWReplacement(SentrySWReplacement) }; \
|
||
|
|
}];
|
||
|
|
|
||
|
|
#define _SentrySWCallOriginal(arguments...) \
|
||
|
|
((__typeof(originalImplementation_))[swizzleInfo getOriginalImplementation])( \
|
||
|
|
self, selector_, ##arguments)
|