美文网首页
iOS Runtime的相关应用

iOS Runtime的相关应用

作者: ikonan | 来源:发表于2018-09-02 10:51 被阅读9次

    一 、Runtime 简介

    Runtime 简称运行时,是系统在运行的时候的一些机制,其中最主要的是消息机制。它是一套比较底层的纯 C 语言 API, 属于一个 C 语言库,包含了很多底层的 C 语言 API。我们平时编写的 OC 代码,在程序运行过程时,其实最终都是转成了 runtime 的 C 语言代码。如下所示:

    // OC代码:
    [Person coding];
    
    //运行时 runtime 会将它转化成 C 语言的代码:
    objc_msgSend(Person, @selector(coding));
    

    二 、相关函数

    // 遍历某个类所有的成员变量
    class_copyIvarList
    
    // 遍历某个类所有的方法
    class_copyMethodList
    
    // 获取指定名称的成员变量
    class_getInstanceVariable
    
    // 获取成员变量名
    ivar_getName
    
    // 获取成员变量类型编码
    ivar_getTypeEncoding
    
    // 获取某个对象成员变量的值
    object_getIvar
    
    // 设置某个对象成员变量的值
    object_setIvar
    
    // 给对象发送消息
    objc_msgSend
    

    三 、相关应用

    • 更改属性值
    • 动态添加属性
    • 动态添加方法
    • 交换方法的实现
    • 拦截并替换方法
    • 在方法上增加额外功能

    四 、代码实现

    要使用Runtime,要先引入头文件#import <objc/runtime.h>

    1. 用 runtime 修改一个对象的属性值

    - (void)updatePropertyValue {
        unsigned int count = 0;
        // 动态获取类中的所有属性(包括私有)
        Ivar *ivar = class_copyIvarList(_person.class, &count);
        // 遍历属性找到对应字段
        for (int i=0; i<count; i++) {
            Ivar tempIvar = ivar[i];
            const char *varChar = ivar_getName(tempIvar);
            NSString *varStr = [NSString stringWithUTF8String:varChar];
            if ([varStr isEqualToString:@"_name"]) {
                // 修改对应的字段值
                object_setIvar(_person, tempIvar, @"更改属性值成功");
                break;
            }
        }
        
        NSLog(@">>>>>person.name:%@",_person.name);
    }
    

    2. 动态添加属性

    用 Runtime 为一个类添加属性, iOS 分类里一般会这样用, 我们建立一个分类, Person+Property.h, 并添加以下代码:

    #import "Person.h"
    @interface Person (Property)
    - (NSInteger)age;
    - (void)setAge:(NSInteger)age
    
    @end
    
    
    #import "Person+Property.h"
    #import <objc/runtime.h>
    
    @implementation Person (Property)
    
    - (NSInteger)age {
        return [objc_getAssociatedObject(self, @"age") integerValue];
    }
    
    -(void)setAge:(NSInteger)age{
        objc_setAssociatedObject(self, @"age", @(age), OBJC_ASSOCIATION_ASSIGN);
    }
    
    @end
    

    这样只要引用Person+Property.h, 用 NSObject 创建的对象就会有一个 age 属性, 我们可以直接这样写:

    _person.age = 29;
    NSLog(@">>>>>person.age:%@",@(_person.age));
    

    3. 动态添加方法

    person 类中没有 coding 方法,我们用 runtime 给 person 类添加了一个名字叫 coding 的方法,最终再调用coding方法做出相应. 下面代码的几个参数需要注意一下:

    - (void)addMethod {
        /*
         动态添加 coding 方法
         (IMP)codingOC 意思是 codingOC 的地址指针;
         "v@:" 意思是,v 代表无返回值 void,
                如果是 i 则代表 int;
                      @ 代表 id sel;
                      : 代表 SEL _cmd;
         “v@:@@” 意思是,两个参数的没有返回值。
         */
        class_addMethod([_person class], @selector(coding), (IMP)codingOC, "v@:");
        if ([_person respondsToSelector:@selector(coding)]) {
            [_person performSelector:@selector(coding)];
            NSLog(@">>>>>:添加方法成功");
        } else {
            NSLog(@">>>>>:添加方法失败");
        }
    }
    
    //编写 codingOC 的实现
    void codingOC(id self, SEL _cmd) {
        NSLog(@"执行方法内容");
    }
    
    

    4. 交换方法的实现

    某个类有两个方法, 比如 person 类有两个方法, eat 方法与 sleep 方法, 我们用 runtime 交换一下这两个方法, 就会出现这样的情况, 当我们调用 eat 的时候, 执行的是 sleep, 当我们调用 sleep 的时候, 执行的是 eat。

    - (void)exchangeImplementations {
        NSLog(@">>>>>交换前");
        [_person eat];
        [_person sleep];
        
        
        Method oriMethod = class_getInstanceMethod([_person class], @selector(eat));
        Method curMethod = class_getInstanceMethod([_person class], @selector(sleep));
        method_exchangeImplementations(oriMethod, curMethod);
        
        NSLog(@">>>>>交换后");
        [_person eat];
        [_person sleep];
    }
    

    5. 拦截并替换方法

    这个功能和上面的其实有些类似, 拦截并替换方法可以拦截并替换同一个类的, 也可以在两个类之间进行, 我这里用了两个不同的类, 下面是简单的代码实现.

    - (void)interceptAndExchange {
        Person *person = [Person new];
        Dog *dog = [Dog new];
        NSLog(@">>>>>拦截前");
        [person sleep];
        
        Method oriMethod = class_getInstanceMethod([person class], @selector(sleep));
        Method curMethod = class_getInstanceMethod([dog class], @selector(sleep));
        method_exchangeImplementations(oriMethod, curMethod);
        
        NSLog(@">>>>>拦截后");
        [person sleep];
    }
    

    6. 在方法上增加额外功能

    这个使用场景还是挺多的, 比如我们需要记录 APP 中某一个按钮的点击次数, 这个时候我们便可以利用 runtime 来实现这个功能. 我这里写了个 UIButton 的子类, 然后在 + (void)load 中用 runtime 给它增加了一个功能, 核心代码如下:

    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Method oriMethod = class_getInstanceMethod(self.class, @selector(sendAction:to:forEvent:));
            Method cusMethod = class_getInstanceMethod(self.class, @selector(customSendAction:to:forEvent:));
            // 判断自定义的方法是否实现, 避免崩溃
            BOOL addSuccess = class_addMethod(self.class, @selector(sendAction:to:forEvent:), method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
            if (addSuccess) {
                // 没有实现, 将源方法的实现替换到交换方法的实现
                class_replaceMethod(self.class, @selector(customSendAction:to:forEvent:), method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
            } else {
                // 已经实现, 直接交换方法
                method_exchangeImplementations(oriMethod, cusMethod);
            }
        });
    }
    

    好吧,先就到这里
    更多请参考 iOS 开发中 runtime 常用的几种方法
    基础知识请参考iOS开发·runtime原理与实践: 基本知识篇

    上一篇 RunLoop入门

    相关文章

      网友评论

          本文标题:iOS Runtime的相关应用

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