Runtime浅析

什么是Runtime

  • C语言是一门静态语言,在编译阶段已确定所有的数据类型,函数方法。
  • Objective-C是一门动态语言,在编译时是不知道具体的变量类型,函数方式,是在运行阶段才确定相关类型,函数。因此我们可以动态去修改相关函数调用,变量等,使OC变得更灵活。
  • Objective-C的运行时机制叫做Runtime。
  • Runtime实际是一个库,OC通过Runtime去调用底层C语言方法。

消息转发机制

所有的Objective-C方法,在编译时都是转化为对C方法objc_msgsend()的调用
1.先来看看OC正常的函数调用
先创建一个Test类

// .h文件
@interface Test : NSObject
- (void)run;
- (void)eatFood:(NSString *)food;
+ (void)run;
@end

//.m文件
@implementation Person
- (void)run {
    NSLog(@"Run方法运行");
}
- (void)eatFood:(NSString *)food {
    NSLog(@"吃%@", food);
}
+ (void)run {
    NSLog(@"执行了类方法Run");
}
@end

在viewController中调用Test.h,并在viewDidLoad执行

#import "Test.h"
- (void)viewDidLoad {
     [super viewDidLoad];

    Test *obj = [Test new];
    //执行run方法
    [obj run];
    //执行eat方法
    [obj eatFood:@"水果"];
    //执行类方法
    [Test run];
}

2.通过Runtime去调用方法

#import "Test.h"
//调用Runtime需要引入对应库
#import <objc/message.h>

- (void)viewDidLoad {
     [super viewDidLoad];

    Test *obj = [Test new];
    objc_msgSend(obj, @selector(run));
    objc_msgSend(obj, @selector(eatFood:), @"水果");
    objc_msgSend([Test class], @selector(run));
}

objc_msgSend中可以传入多个参数

  • 第一个参数为执行的对象(类方法,则传类,类实际也是一种对象)
  • 第二个参数为调用的方法
  • 第三个及后序参数为可选参数,传入第二个参数方法需要的参数
    执行结果如图:


    运行结果

    2、注意苹果是禁止使用objc_msgsend方法的,要使用需要关闭对应检测Build Setting -> Enable Strict Checking of objc_msgSend Calls改为No


    修改设置

交换方法

1.交换方法是我们开发中经常运用到,在很多老得项目中,有大量使用一个老方法,如果一个又一个去修改会耗费大量的时间,所以可以通过RunTime交换方法,快速替换。
2.举个例子

#在原有方法中调用了[NSURL URLWithString:]
NSURL *url = [NSURL URLWithString:@"www.baidu.com"];
NSLog(@"%@", url);
#如果url中不包含中文,该方法正常执行,但若插入中文,url会为空
url = [NSURL URLWithString:@"www.baidu.com\中文"];
NSLog(@"%@", url);

3.这时后就需要替换全部URLWithString方法。
我们可以创建一个NSURL的分类,实现一个新的方法NewWithString

@implementation NSURL (url)
+ (instancetype)NewWithString:(NSString *)str {
    NSURL *url = [NSURL URLWithString:str];
    if (!url) {
        NSLog(@"URL为空");
    }
    return url;
}
@end

4.根据load方法在加载进入OC运行时被执行,可以在load方法中实现函数替换

  • 我们需要用到class_getClassMethod获取到原有类方法和新的类方法(获取实例方法则通过class_getInstanceMethod)
  • 通过method_exchangeImplementations来交换两个方法
+ (void)load {
    //获取老方法
    Method oldMethod = class_getClassMethod([NSURL class], @selector(URLWithString:));
    //获取新方法
    Method newMethod = class_getClassMethod([NSURL class], @selector(NewWithString:));
    //交换方法
    method_exchangeImplementations(oldMethod, newMethod);
}
  • 注意:
    如果直接执行会陷入死循环。
    因为在原有方法中,各个函数的方法实现如图(ps 图有点丑,见谅):



    经过修改后变为:



    因为我们在NewWithString执行:
NSURL *url = [NSURL URLWithString:str];

它会不断调用NewWithString的函数实现,导致死循环。需改成:

NSURL *url = [NSURL NewWithString:str];

执行结果如图:


方法懒加载

节约性能,我们经常用到属性的懒加载,函数方法同样可以懒加载
1.同样使用Test.h文件,不定义任何方法
2.我们在viewController直接调用

objc_msgSend(obj, @selector(lazyMethod));

因为没有定义方法,函数会直接报错。
3.实际上,在没有找到方法时,会执行对应类的

//对应类方法
+ (BOOL)resolveClassMethod:(SEL)sel;
//对应实例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel;

4.我们先在Test.m中定义一个需要懒加载的方法

//id self, SEL _cmd为隐式参数可不写
int newMethod(id self, SEL _cmd) {
    NSLog(@"执行了");
    return 0;
}

5.在发现方法为需要懒加载的方法时,将函数加载进去

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if ([NSStringFromSelector(sel) isEqualToString:@"lazyMethod"]) {
        class_addMethod([Test class], sel, newMethod, "i@:");
    }
    return [super resolveInstanceMethod:sel];
}

class_addMethod有四个参数
1.对应类
2.方法名
3.添加的方法
4.方法需要使用的参数,newMethod的返回值int 对应"i", id self对应"@",SEL对应":"具体对应可参考文档
执行结果如图:

执行结果

KVO底层实现

kvo原理是创建对应类的子类,在子类中重写set方法,同时修改isa指针指向新创建的类。
1.创建一个Person类,并添加name属性用于监听测试。

@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@end

2.创建NSObject分类,添加新的监听方法

@interface NSObject (KVO)
- (void)new_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
@end

3.在ViewController添加监听

- (void)viewDidLoad {
    [super viewDidLoad];
    _p = [Person new];
    NSLog(@"修改前的类%@", [_p class]);
    //使用自定义KVO监听
    [self.p new_addObserver:self forKeyPath:@"name" options:0 context:nil];
    NSLog(@"修改后的类%@", [_p class]);
 }

//name改变后调用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"监听到了name改变:%@",_p.name);
}

//点击改变值,触发KVO
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    static int i = 0;
    i++;
    _p.name = [NSString stringWithFormat:@"%d", i];
}

4.NSObject分类实现

- (void)new_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
    //self 被观察者
    //observer 观察者
    //1、自定义子类
    //获取self类名
    NSString *oldClassName = NSStringFromClass([self class]);
    //创建self的子类
    NSString *newClassName = [@"new_" stringByAppendingString:oldClassName];
    const char *newName = [newClassName UTF8String];
    //动态生成类
    Class newClass = objc_allocateClassPair([self class], newName, 0);
    //注册类 类加入内存中可供调用
    objc_registerClassPair(newClass);
    SEL s = NSSelectorFromString([NSString stringWithFormat:@"set%@:", [keyPath capitalizedString]]);
    //2、添加set方法
    class_addMethod(newClass, s, (IMP)setObject, "v:@:@");
    //3、修改isa指针
    object_setClass(self, newClass);
    //保存观察者对象
    objc_setAssociatedObject(self, "objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    objc_setAssociatedObject(self, "keyPath", keyPath, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

void setObject(id self, SEL _cmd, id newName){
    //1.调用super的set方法
    id class = [self class];
    //改变self的isa指针
    object_setClass(self, class_getSuperclass(class));
//    objc_msgSend(self, @selector(setName:), newName);
    SEL s = NSSelectorFromString([NSString stringWithFormat:@"set%@:", [objc_getAssociatedObject(self, "keyPath") capitalizedString]]);
    objc_msgSend(self, s, newName);
    NSLog(@"修改完毕");
    //拿到观察者
    id objc = objc_getAssociatedObject(self, "objc");
    //通知观察者
    objc_msgSend(objc, @selector(observeValueForKeyPath:ofObject:change:context:), objc_getAssociatedObject(self, "keyPath"),self, nil, nil);
    //改回子类类型
    object_setClass(self, class);
}

执行结果如图:


执行结果
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容