美文网首页
Runtime 三:runtime 中常用的 API 介绍

Runtime 三:runtime 中常用的 API 介绍

作者: 小心韩国人 | 来源:发表于2019-12-06 11:43 被阅读0次

    当目前为止我们已经了解了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)动态替换方法
    • ``获取方法的相关信息 (带有copy的需要调用free去释放)
    • 获取方法相关信息:
    • 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<NSCopying>)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<NSCopying>)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 介绍

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