美文网首页
iOS Runtime的工具类

iOS Runtime的工具类

作者: 心猿意码_ | 来源:发表于2022-05-26 10:07 被阅读0次

QMRuntimeHelper.h


#import <Foundation/Foundation.h>

typedef void(^RuntimeHookBlock)(id ,NSArray *);

@interface QMRuntimeHelper : NSObject

/**
 *  用一个新的方法替换类中的一个已经存在的方法
 *
 *  @param aclass            要修改的类
 *  @param originalSelector 要替换的方法的选择器
 *  @param swizzledSelector 新方法的选择器
 *  @param isInstanceMethod 是否实例方法
 */
+ (void)setMethodSwizzlingForClass:(Class)aclass originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector isInstanceMethod:(BOOL)isInstanceMethod;

/**
 *  在运行时判断类中是否包含某个属性
 *
 *  @param aclass        类
 *  @param propertyName 属性名
 *
 *  @return 该属性是否存在
 */
+ (BOOL)isClass:(Class)aclass hasProperty:(NSString*)propertyName;

/**
 *  在运行时判断类中是否包含某个实例方法,类似于NSObject的instanceRespondsToSelector
 *
 *  @param aclass              类
 *  @param instanceMethodName 方法名
 *
 *  @return 该方法是否存在
 */
+ (BOOL)isClass:(Class)aclass hasInstanceMethod:(NSString*)instanceMethodName;

/**
 *  在运行时判断类中是否包含某个类方法
 *
 *  @param aclass           类
 *  @param classMethodName 方法名
 *
 *  @return 该方法是否存在
 */
+ (BOOL)isClass:(Class)aclass hasClassMethod:(NSString *)classMethodName;

/**
 *  在运行时判断一个类是否存在
 *
 *  @param className 类名
 *
 *  @return 是否存在
 */
+ (BOOL)isClassExist:(NSString *)className;

/**
 *  修改类中的一个方法,往里面添加一些代码(本质上是方法置换)
 *
 *  @param aclass              要修改的类
 *  @param originalMethodName 要修改的方法名
 *  @param isInstanceMethod   是否实例方法
 *  @param block              增加的代码
 *
 *  @return 是否添加-成功
 */
+ (BOOL)hookMethodForClass:(Class)aclass originalMethod:(NSString*)originalMethodName isInstanceMethod:(BOOL)isInstanceMethod hookedBlock:(RuntimeHookBlock)block;

/**
 *  获取类中某个方法的实现block
 *
 *  @param aclass          类
 *  @param methodSelector 方法选择器
 *
 *  @return 该方法对应的block
 */
+ (id)methodBlockForClass:(Class)aclass method:(SEL)methodSelector;

/**
 *  获取所有遵循了指定协议的类
 *
 *  @param protocol 要遵循的协议
 *
 *  @return 遵循了协议的类
 */
+ (NSArray *)getClassesThatConfirmToProtocol:(Protocol*)protocol;

@end

QMRuntimeHelper.m


#import "QMRuntimeHelper.h"
#import <objc/runtime.h>

@implementation QMRuntimeHelper

+(void)setMethodSwizzlingForClass:(Class)class originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector isInstanceMethod:(BOOL)isInstanceMethod {
    Method originalMethod;
    Method swizzledMethod;
    
    if (isInstanceMethod) {
        originalMethod = class_getInstanceMethod(class, originalSelector);
        swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    } else {
        originalMethod = class_getClassMethod(class, originalSelector);
        swizzledMethod = class_getClassMethod(class, swizzledSelector);
    }

 
    BOOL didAddMethod =
    class_addMethod(class,
                    originalSelector,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));
    
    if (didAddMethod) {
        class_replaceMethod(class,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

+ (BOOL)isClass:(Class)class hasProperty:(NSString *)propertyName {
    return (class_getProperty(class, [propertyName UTF8String]) != NULL);
}

+ (void)printPropertiesOfClass:(Class)class {
    unsigned int count = 0;
    objc_property_t *propList = class_copyPropertyList(class, &count);
    for (int i=0; i<count; i++) {
        objc_property_t property = propList[i];
        const char* char_f =property_getName(property);
        NSString *propertyName = [NSString stringWithUTF8String:char_f];
        MYLog(@"proprty = %@",propertyName);
    }
    free(propList);

}


+(BOOL)isClass:(Class)class hasInstanceMethod:(NSString*)instanceMethodName {
    SEL selector = NSSelectorFromString(instanceMethodName);
    Method method = class_getInstanceMethod(class, selector);
    return (method != NULL);
}

+(BOOL)isClass:(Class)class hasClassMethod:(NSString *)classMethodName {
    SEL selector = NSSelectorFromString(classMethodName);
    Method method = class_getClassMethod(class,selector);
    return (method != NULL);
}

+ (BOOL)isClassExist:(NSString *)className {
    Class class = objc_getClass([className UTF8String]);
    return (class!=nil);
}

+ (BOOL)hookMethodForClass:(Class)class originalMethod:(NSString*)originalMethodName isInstanceMethod:(BOOL)isInstanceMethod hookedBlock:(RuntimeHookBlock)block {
    NSString *hookedMethodName = [NSString stringWithFormat:@"hook%@",originalMethodName];
    SEL hookedSelector = NSSelectorFromString(hookedMethodName);
    
    Method originalMethod;
    if (isInstanceMethod) {
        originalMethod = class_getInstanceMethod(class, NSSelectorFromString(originalMethodName));
    } else {
        originalMethod = class_getClassMethod(class, NSSelectorFromString(originalMethodName));
    }
  
    //return type
    char* returnType = (char*)malloc(sizeof(char));
    returnType = method_copyReturnType(originalMethod);
//    printf("returnType %s ", returnType);
    //argument type
    unsigned int argumentCount = method_getNumberOfArguments(originalMethod);
    //最多保存4个参数,前面两个固定的,后面是可变的
    char* argumentType = malloc(sizeof(char)*4);
    memset(argumentType, 0, 4);
    for (unsigned int i = 0; i<argumentCount; i++) {
        method_getArgumentType(originalMethod, i, &argumentType[i], 1);
    }
    
    IMP imp = NULL;
    char *argTypes = malloc(sizeof(char) * (argumentCount+1));
    memset(argTypes, 0, argumentCount +1);
    argTypes[0] = *returnType;
//    argTypes = strcpy(argTypes, returnType);
    if (argumentCount == 2) {
        imp = imp_implementationWithBlock(^id (id _self) {
            id object = class;
            if (isInstanceMethod) {
                object = _self;
            }
            id returnValue = [QMRuntimeHelper performHookedMethod:hookedSelector withArgs:@[] forObject:object isInstanceMethod:isInstanceMethod];
            if (block) {
                block(_self, @[]);
            }

            return returnValue;
        });
        argTypes = strcat(argTypes, argumentType);

    } else if (argumentCount == 3) {
        imp = imp_implementationWithBlock(^id (id _self, id arg1){
            id returnValue;
            id object = class;
            if (isInstanceMethod) {
                object = _self;
            }
            if (arg1) {
                returnValue = [QMRuntimeHelper performHookedMethod:hookedSelector withArgs:@[arg1] forObject:object isInstanceMethod:isInstanceMethod];
                if (block) {
                    block(_self, @[arg1]);
                }
            } else {
                returnValue = [QMRuntimeHelper performHookedMethod:hookedSelector withArgs:@[] forObject:object isInstanceMethod:isInstanceMethod];
                if (block) {
                    block(_self, @[]);
                }
            }

            return returnValue;
        });
        argTypes = strcat(argTypes, argumentType);

    } else if (argumentCount == 4) {
        imp = imp_implementationWithBlock(^id (id _self, id arg1, id arg2){
            id object = class;
            if (isInstanceMethod) {
                object = _self;
            }
            id returnValue = [QMRuntimeHelper performHookedMethod:hookedSelector withArgs:@[arg1,arg2] forObject:object isInstanceMethod:isInstanceMethod];;

            if (block) {
                block(_self,@[arg1,arg2]);
            }
            return returnValue;
        });
        argTypes = strcat(argTypes, argumentType) ;


    }
    
    IMP originImp = method_getImplementation(originalMethod);
    BOOL succ;
    if (isInstanceMethod) {
        succ = class_addMethod(class, hookedSelector, originImp, argTypes);
    } else {
        succ = class_addMethod(object_getClass(class), hookedSelector, originImp, argTypes);
    }
    
    free(argTypes);
    free(returnType);
    method_setImplementation(originalMethod, imp);
//    free(returnType);
    return succ;
}

+ (id)performHookedMethod:(SEL)hookedSelector withArgs:(NSArray*)args forObject:(id)object isInstanceMethod:(BOOL)isInstanceMethod {
    id returnValue;
//    id arg1,arg2;
//    if (args.count >0) {
//        arg1 = args[0];
//    }
//    if (args.count >1) {
//        arg2 = args[1];
//    }
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
   
    if ([object respondsToSelector:hookedSelector]) {
        returnValue =[object performSelector:hookedSelector];
    }
   
#pragma clang diagnostic pop
    return returnValue;
}

+ (id)methodBlockForClass:(Class)class method:(SEL)methodSelector {
    IMP imp = class_getMethodImplementation(class, methodSelector);
    return imp_getBlock(imp);
}

+ (NSArray *)getClassesThatConfirmToProtocol:(Protocol *)protocol {
    NSMutableArray *classes = [NSMutableArray array];
    unsigned int classCount;
    Class* classList = objc_copyClassList(&classCount);

    int i;
    for (i=0; i<classCount; i++) {
        const char *className = class_getName(classList[i]);
        Class thisClass = objc_getClass(className);
        if (class_conformsToProtocol(thisClass, protocol)) {
            [classes addObject:thisClass];
        }
    }
    free(classList);
    return classes;
}

@end


相关文章

网友评论

      本文标题:iOS Runtime的工具类

      本文链接:https://www.haomeiwen.com/subject/mejuprtx.html