2023-04-03 11:04:31 +08:00

242 lines
8.7 KiB
Objective-C

#import "TDSwizzler.h"
#import <objc/runtime.h>
#import "TDLogging.h"
#define MAPTABLE_ID(x) (__bridge id)((void *)x)
#define MIN_ARGS 2
#define MAX_ARGS 4
@interface TDSwizzle : NSObject
@property (nonatomic, assign) Class class;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, assign) IMP originalMethod;
@property (nonatomic, assign) uint numArgs;
@property (nonatomic, copy) NSMapTable *blocks;
- (instancetype)initWithBlock:(swizzleBlock)aBlock
named:(NSString *)aName
forClass:(Class)aClass
selector:(SEL)aSelector
originalMethod:(IMP)aMethod;
@end
static NSMapTable *swizzles;
static void td_swizzledMethod_2(id self, SEL _cmd) {
Method aMethod = class_getInstanceMethod([self class], _cmd);
TDSwizzle *swizzle = (TDSwizzle *)[swizzles objectForKey:MAPTABLE_ID(aMethod)];
if (swizzle) {
((void(*)(id, SEL))swizzle.originalMethod)(self, _cmd);
NSEnumerator *blocks = [swizzle.blocks objectEnumerator];
swizzleBlock block;
while((block = [blocks nextObject])) {
block(self, _cmd);
}
}
}
static void td_swizzledMethod_3(id self, SEL _cmd, id arg) {
Method aMethod = class_getInstanceMethod([self class], _cmd);
TDSwizzle *swizzle = (TDSwizzle *)[swizzles objectForKey:MAPTABLE_ID(aMethod)];
if (swizzle) {
((void(*)(id, SEL, id))swizzle.originalMethod)(self, _cmd, arg);
NSEnumerator *blocks = [swizzle.blocks objectEnumerator];
swizzleBlock block;
while((block = [blocks nextObject])) {
block(self, _cmd, arg);
}
}
}
static void td_swizzledMethod_4(id self, SEL _cmd, id arg, id arg2) {
Method aMethod = class_getInstanceMethod([self class], _cmd);
TDSwizzle *swizzle = (TDSwizzle *)[swizzles objectForKey:(__bridge id)((void *)aMethod)];
if (swizzle) {
((void(*)(id, SEL, id, id))swizzle.originalMethod)(self, _cmd, arg, arg2);
NSEnumerator *blocks = [swizzle.blocks objectEnumerator];
swizzleBlock block;
while((block = [blocks nextObject])) {
block(self, _cmd, arg, arg2);
}
}
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wstrict-prototypes"
static void (*td_swizzledMethods[MAX_ARGS - MIN_ARGS + 1])() = {td_swizzledMethod_2, td_swizzledMethod_3, td_swizzledMethod_4};
#pragma clang diagnostic pop
@implementation TDSwizzler
+ (void)load {
swizzles = [NSMapTable mapTableWithKeyOptions:(NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality)
valueOptions:(NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality)];
}
+ (TDSwizzle *)swizzleForMethod:(Method)aMethod {
return (TDSwizzle *)[swizzles objectForKey:MAPTABLE_ID(aMethod)];
}
+ (void)removeSwizzleForMethod:(Method)aMethod {
[swizzles removeObjectForKey:MAPTABLE_ID(aMethod)];
}
+ (void)setSwizzle:(TDSwizzle *)swizzle forMethod:(Method)aMethod {
[swizzles setObject:swizzle forKey:MAPTABLE_ID(aMethod)];
}
+ (BOOL)isLocallyDefinedMethod:(Method)aMethod onClass:(Class)aClass {
uint count;
BOOL isLocal = NO;
Method *methods = class_copyMethodList(aClass, &count);
for (NSUInteger i = 0; i < count; i++) {
if (aMethod == methods[i]) {
isLocal = YES;
break;
}
}
free(methods);
return isLocal;
}
+ (void)swizzleSelector:(SEL)aSelector
onClass:(Class)aClass
withBlock:(swizzleBlock)aBlock
named:(NSString *)aName {
Method aMethod = class_getInstanceMethod(aClass, aSelector);
if (!aMethod) {
TDLogInfo(@"SwizzleException:Cannot find method for %@ on %@",NSStringFromSelector(aSelector), NSStringFromClass(aClass));
return;
}
uint numArgs = method_getNumberOfArguments(aMethod);
if (numArgs < MIN_ARGS || numArgs > MAX_ARGS) {
TDLogError(@"Cannot swizzle method with %d args", numArgs);
}
IMP swizzledMethod = (IMP)td_swizzledMethods[numArgs - 2];
[TDSwizzler swizzleSelector:aSelector onClass:aClass withBlock:aBlock andSwizzleMethod:swizzledMethod named:aName];
}
+ (void)swizzleSelector:(SEL)aSelector
onClass:(Class)aClass
withBlock:(swizzleBlock)aBlock
andSwizzleMethod:(IMP)aSwizzleMethod
named:(NSString *)aName {
Method aMethod = class_getInstanceMethod(aClass, aSelector);
if (!aMethod) {
TDLogError(@"Cannot find method for %@ on %@", NSStringFromSelector(aSelector), NSStringFromClass(aClass));
}
BOOL isLocal = [self isLocallyDefinedMethod:aMethod onClass:aClass];
TDSwizzle *swizzle = [self swizzleForMethod:aMethod];
if (isLocal) {
if (!swizzle) {
IMP originalMethod = method_getImplementation(aMethod);
// Replace the local implementation of this method with the swizzled one
method_setImplementation(aMethod, aSwizzleMethod);
// Create and add the swizzle
@try {
swizzle = [[TDSwizzle alloc] initWithBlock:aBlock named:aName forClass:aClass selector:aSelector originalMethod:originalMethod];
} @catch (NSException *exception) {
TDLogError(@"%@ error: %@", self, exception);
}
[self setSwizzle:swizzle forMethod:aMethod];
} else {
[swizzle.blocks setObject:aBlock forKey:aName];
}
} else {
IMP originalMethod = swizzle ? swizzle.originalMethod : method_getImplementation(aMethod);
// Add the swizzle as a new local method on the class.
if (!class_addMethod(aClass, aSelector, aSwizzleMethod, method_getTypeEncoding(aMethod))) {
TDLogError(@"Could not add swizzled for %@::%@, even though it didn't already exist locally", NSStringFromClass(aClass), NSStringFromSelector(aSelector));
}
// Now re-get the Method, it should be the one we just added.
Method newMethod = class_getInstanceMethod(aClass, aSelector);
if (aMethod == newMethod) {
TDLogError(@"Newly added method for %@::%@ was the same as the old method", NSStringFromClass(aClass), NSStringFromSelector(aSelector));
}
TDSwizzle *newSwizzle = [[TDSwizzle alloc] initWithBlock:aBlock named:aName forClass:aClass selector:aSelector originalMethod:originalMethod];
[self setSwizzle:newSwizzle forMethod:newMethod];
}
}
+ (void)unswizzleSelector:(SEL)aSelector onClass:(Class)aClass {
Method aMethod = class_getInstanceMethod(aClass, aSelector);
TDSwizzle *swizzle = [self swizzleForMethod:aMethod];
if (swizzle) {
method_setImplementation(aMethod, swizzle.originalMethod);
[self removeSwizzleForMethod:aMethod];
}
}
/*
Remove the named swizzle from the given class/selector. If aName is nil, remove all
swizzles for this class/selector
*/
+ (void)unswizzleSelector:(SEL)aSelector onClass:(Class)aClass named:(NSString *)aName {
Method aMethod = class_getInstanceMethod(aClass, aSelector);
TDSwizzle *swizzle = [self swizzleForMethod:aMethod];
if (swizzle) {
if (aName) {
[swizzle.blocks removeObjectForKey:aName];
}
if (!aName || [swizzle.blocks count] == 0) {
method_setImplementation(aMethod, swizzle.originalMethod);
[self removeSwizzleForMethod:aMethod];
}
}
}
@end
@implementation TDSwizzle
- (instancetype)init {
if ((self = [super init])) {
self.blocks = [NSMapTable mapTableWithKeyOptions:(NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPersonality)
valueOptions:(NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality)];
}
return self;
}
- (instancetype)initWithBlock:(swizzleBlock)aBlock
named:(NSString *)aName
forClass:(Class)aClass
selector:(SEL)aSelector
originalMethod:(IMP)aMethod {
if ((self = [self init])) {
self.class = aClass;
self.selector = aSelector;
self.originalMethod = aMethod;
[self.blocks setObject:aBlock forKey:aName];
}
return self;
}
- (NSString *)description {
NSString *descriptors = @"";
NSString *key;
NSEnumerator *keys = [self.blocks keyEnumerator];
while ((key = [keys nextObject])) {
descriptors = [descriptors stringByAppendingFormat:@"\t%@ : %@\n", key, [self.blocks objectForKey:key]];
}
return [NSString stringWithFormat:@"Swizzle on %@::%@ [\n%@]", NSStringFromClass(self.class), NSStringFromSelector(self.selector), descriptors];
}
@end