Runtime 三:runtime 中常用的 API 介绍

当目前为止我们已经了解了runtime 是如何查找方法,以及如何缓存方法; 如何关联对象,给分类添加成员变量;Class 类的底层数据结构是怎样的,如何存储类信息的等等...今天我们就了解一下runtime常用的API.

一: 类相关的 API

  • Class object_getClass(id _Nullable obj) 获取 isa指向的Class
    object_getClass
  • Class object_setClass(id _Nullable obj, Class _Nonnull cls) 设置 isa的指向的Class
    object_setClass
  • BOOL object_isClass(id _Nullable obj)判断一个OC对象是否为Class
  • BOOL class_isMetaClass(Class _Nullable cls)判断一个Class是否为元类
  • Class class_getSuperclass(Class _Nullable cls)获取父类
    superclass实例对象和类对象都可以调用,他们的区别是:
- (Class)superclass {
    return [self class]->superclass;
}

+ (Class)superclass {
    return self->superclass;
}
  • Class objc_allocateClassPair(Class superclass, const char * name, size_t extraBytes)
    动态创建一个类,(参数:父类,类名,额外的存储空间)
  • objc_registerClassPair(Class cls)注册一个类 (要在类注册之前添加成员变量)

void run(id self,SEL _cmd){
    NSString *name = [self valueForKey:@"_name"];
    int age = [[self valueForKey:@"_age"] intValue];
    NSLog(@"my name is %@ age is %d",name,age);
}

int main(int argc, const char * argv[]) {
        // 动态创建类
        NSString *className = @"Cat";
        const char * charClassName = [className UTF8String];
        //动态创建一个类
        Class newClass = objc_allocateClassPair([NSObject class], charClassName, 1);
        //给这个类添加成员变量
        class_addIvar(newClass, "_name", 16, 1, @encode(NSString *));
        class_addIvar(newClass, "_age", 4, 1, @encode(int));
        //给这个类添加方法
        class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
        //注册类
        objc_registerClassPair(newClass);
        
        id cat = [[newClass alloc]init];
        [cat setValue:@10 forKey:@"_age"];
        [cat setValue:@"Tom" forKey:@"_name"];
        [cat run];
}
// 打印:
2019-12-05 15:39:22.480797+0800 runtime常用API介绍[2213:384292] my name is Tom age is 10

二: 成员变量相关的 API

  • Ivar class_getInstanceVariable 获取一个实例变量的信息
    image.png
  • object_setIvar(id obj, Ivar ivar, id value)设置实例变量的值
    object_setIvar
  • id object_getIvar(id = obj, Ivar ivar)获取成员变量的值
    object_getIvar
  • BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)动态添加成员变量 (已经注册的类是不能添加成员变量的)
  • const char *ivar_getName(Ivar v) 获取成员变量 name
  • const char * ivar_getTypeEncoding(Ivar v) 获取成员变量字符串编码
  • Ivar * class_copyIvarList(Class cls, unsigned int * outCount)拷贝实例变量列表,最后需要调用free释放.
    用途一: 获取系统类私有的成员变量 (这种方式在 iOS13 后 已经被禁用了,iOS13 后系统禁止访问一些私有的成员变量)
    unsigned int count;
    Ivar *ivar = class_copyIvarList([UITextField class], &count);
    for (int i = 0; i < count; i ++) {
        Ivar iva = ivar[I];
        NSLog(@"%s",ivar_getName(iva));
    }
    self.nameTF.placeholder = @"请输入姓名";
    [self.nameTF setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
    free(ivar);

用途二:字典转模型

+ (instancetype)json2Model:(NSDictionary *)json{
    id obj = [[self alloc]init];
    unsigned int count;
    Ivar *ivars = class_copyIvarList([self class], &count);
    //遍历所有的成员变量
    for (int i = 0; i < count; i ++) {
        Ivar iva = ivars[I];
        NSMutableString *ivarStr = [NSMutableString stringWithUTF8String:ivar_getName(iva)];
        //去掉成员变量前面的 _
        [ivarStr deleteCharactersInRange:NSMakeRange(0, 1)];
        
        [obj setValue:json[ivarStr] forKey:ivarStr];
    }
    
    return obj;
}

这样写会有很多问题,这只是个思路,仅供参考

用途三:归档,解档

- (instancetype)initWithCoder:(NSCoder *)coder{
    if (self = [super init]) {
        unsigned int count;
        Ivar *ivars = class_copyIvarList([self class], &count);
        //遍历所有的成员变量
        for (int i = 0; i < count; i ++) {
            Ivar iva = ivars[I];
            NSMutableString *ivarStr = [NSMutableString stringWithUTF8String:ivar_getName(iva)];
            //去掉成员变量前面的 _
            [ivarStr deleteCharactersInRange:NSMakeRange(0, 1)];
            //从文件中取出值
            id value = [coder decodeObjectForKey:ivarStr];
            //赋值到对象中
            [self setValue:value forKey:ivarStr];
        }
    }
    return self;
}


- (void)encodeWithCoder:(NSCoder *)coder{
    
    unsigned int count;
    Ivar *ivars = class_copyIvarList([self class], &count);
    //遍历所有的成员变量
    for (int i = 0; i < count; i ++) {
        Ivar iva = ivars[I];
        NSMutableString *ivarStr = [NSMutableString stringWithUTF8String:ivar_getName(iva)];
        //去掉成员变量前面的 _
        [ivarStr deleteCharactersInRange:NSMakeRange(0, 1)];
        //从对象中取出对应的值
        id value = [self valueForKey:ivarStr];
        //归档到文件中
        [coder encodeObject:value forKey:ivarStr];
    }
}

三: 属性相关的 API

  • objc_property_t class_getProperty(Class cls, const char * name)获取一个属性
  • objc_property_t * class_copyPropertyList(Class cls, unsigned int * outCount)拷贝属性列表 (需要调用 free 释放)
  • BOOL class_addProperty(Class cls, const char * name, const objc_property_attribute_t * attributes, unsigned int attributeCount)动态添加属性
  • void class_replaceProperty(Class cls, const char * name, const objc_property_attribute_t * attributes, unsigned int attributeCount)动态替换属性
  • const char * property_getName(objc_property_t property)获取属性名字
  • const char * property_getAttributes(objc_property_t property)获取属性真实类型

四: 方法相关的 API

  • Method class_getInstanceMethod(Class cls, SEL name)获得一个实例方法

  • Method class_getClassMethod(Class cls, SEL name)获得一个类方法

  • Method _Nonnull * class_copyMethodList(Class cls, unsigned int * outCount)拷贝方法列表 (最后需要调用 free 释放)

  • BOOL class_addMethod(Class cls, SEL name, IMP imp, const char * types)动态添加方法

  • IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char * types)动态替换方法

  • 获取方法相关信息:

  • SEL method_getName(Method m)获取方法名称

  • IMP method_getImplementation(Method m)获取方法实现 IMP

  • const char * method_getTypeEncoding(Method m)获取方法签名

  • unsigned int method_getNumberOfArguments(Method m)获取方法参数数量

  • char * method_copyReturnType(Method m)获取方法返回值类型

  • char * method_copyArgumentType(Method m, unsigned int index)获取方法返回值类型

  • 方法实现相关操作

  • IMP class_getMethodImplementation(Class cls, SEL name)获取方法的实现 IMP

  • IMP method_setImplementation(Method m, IMP imp)修改方法的实现 IMP

  • void method_exchangeImplementations(Method m1, Method m2)交换两个方法的实现
    重点说一下最后3个方法实现相关的API.主要的用法就是交换系统方法的实现,往系统的方法里添加我们自己的逻辑.
    void method_exchangeImplementations(Method m1, Method m2)的交换原理如下图所示:

    method_exchangeImplementations 方法执行之前

    method_exchangeImplementations 方法执行之后

    从上图可以看出来,method_exchangeImplementations只是交换两个Method中的IMP.

用法一:交换UIControl- (void)sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event;方法,拦截所有的点击事件,给UIControl添加一个分类:

+ (void)load{
    Method systemSendAction = class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:));
    Method customSendAction = class_getInstanceMethod([self class], @selector(hook_sendAction:to:forEvent:));
    method_exchangeImplementations(systemSendAction, customSendAction);
}
- (void)hook_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
    //执行系统原来的方法
    [self hook_sendAction:action to:target forEvent:event];
    //TODO  添加自己的业务逻辑
    
}

用法二:拦截NSMutableDictionary的设值方法setObject:forKeyedSubscript:NSDictionary的取值方法objectForKeyedSubscript:

+ (void)load{
    Class mCls = NSClassFromString(@"__NSDictionaryM");
    Method old_set = class_getInstanceMethod(mCls, @selector(setObject:forKeyedSubscript:));
    Method new_set = class_getInstanceMethod([self class], @selector(hook_setObject:forKeyedSubscript:));
    method_exchangeImplementations(old_set, new_set);
    
    Method old_setObj = class_getInstanceMethod(mCls, @selector(setObject:forKey:));
    Method new_setObj = class_getInstanceMethod(mCls, @selector(hook_setObject:forKey:));
    method_exchangeImplementations(old_setObj, new_setObj);
}

//  mutaDIc[@"ok"] = nil 会触发这个方法
- (void)hook_setObject:(id)obj forKeyedSubscript:(id)key{

    if (!obj){
        NSLog(@"obj为nil");
            return;
        };
    [self hook_setObject:obj forKeyedSubscript:key];
}

// [mutaDIc setObject:nil forKey:@"ok"] 会触发这个方法
- (void)hook_setObject:(id)anObject forKey:(id)aKey{
    if (!anObject) {
        return;
    }
    [self hook_setObject:anObject forKey:aKey];
}

用法三:拦截NSMutableArrayinsertObject:atIndex:方法:

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class cls = NSClassFromString(@"__NSArrayM");
        Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
        Method method2 = class_getInstanceMethod(cls, @selector(mj_insertObject:atIndex:));
        method_exchangeImplementations(method1, method2);
    });
}

- (void)mj_insertObject:(id)anObject atIndex:(NSUInteger)index
{
    if (anObject == nil) return;
    
    [self mj_insertObject:anObject atIndex:index];
}

这里只列举几个常用的防止崩溃的用法,大家可以根据具体业务需要拦截系统方法.

你可能感兴趣的:(Runtime 三:runtime 中常用的 API 介绍)