美文网首页
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