前言
最近在准备面试,所以刷一波底层原理;本文主要介绍runtime相关一些知识点
一、runtime的概念
-
runtime简称运行时,OC就是运行时机制,也就是说在运行时候的一些机制,其中最主要的就是消息机制。
-
对于C语言,函数的调用在编译时候就已经决定了调用哪个函数
-
OC的函数,属于运行时调用,在编译的时候并不能真正的决定调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
-
编译阶段,OC调用任何函数,即使并未实现,只要有声明就不会报错;C语言调用未实现函数会报错;
二、runtime的作用
2.1 消息机制
- 方法调用的本质: 实际就是让对象发送消息
- objc_msgSend, 只有对象才能发送消息,因此以objc开头
- 使用消息机制前提,导入头文件
#import<objc/message.h> - 消息机制的原理: 对象根据方法编号
SEL去方法映射表中查找对应的方法实现
2.2.1消息调用的流程
1.通过对象的isa指针去对应的类中查找
2.注册方法编号
3.根据方法编号去查找对应的方法
4.找到最终函数实现地址,根据地址再去方法去调用对应的函数
2.2 交换方法(黑魔法)
-
系统自带的方法不能满足需求的时候,可以给系统自带的方法扩展一些功能,并且还能保持原有的功能;
-
常用的:继承自系统的类,重写方法 -
高端的:使用runtime,交换方法
代码实现runtime交换方法:
-
#import "UIImage+XK_Image.h"
#import <objc/runtime.h>
@implementation UIImage (XK_Image)
// 把类加载进内存的时候调用,并且只会调用一次
+ (void)load{
// 交换方法
Method imageNameMethod = class_getClassMethod(self, @selector(imageNamed:));
Method xk_imageNameMethod = class_getClassMethod(self, @selector(xk_imageNamed:));
// 交换方法地址,相当于交换实现方法
method_exchangeImplementations(imageNameMethod, xk_imageNameMethod);
}
// 特别注意: 不能再分类中重写系统的imageNamed方法,因为会把系统的功能给覆盖掉,而且分类中不能调用super
// 在这的目标就是 能加载图片并且可以完成打印
+ (UIImage *)xk_imageNamed:(NSString *)name{
// 图片
// 在这里就相当于调用了系统的imageNamed:方法
UIImage * image = [UIImage xk_imageNamed:name];
if (image) {
NSLog(@"图片加载成功");
} else {
NSLog(@"图片加载失败");
}
return image;
}
@end
在ViewController调用实现:
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIImage * image = [UIImage imageNamed:@"image1"];
UIImageView * img = [[UIImageView alloc] initWithImage:image];
img.frame = CGRectMake(0, 0, 200, 200);
[self.view addSubview:img];
// 打印结果:
// 2020-09-25 15:45:10.618627+0800 GCDSemephore[3173:139764] 图片加载成功
}
2.3 给分类添加属性
tips:实际开发中,分类是不可以添加属性的,虽然可以用@property声明,也并不会报错,但是他仅仅也是自动生成了get和set的声明,并没有带下划线的属性和方法实现的生成,所以可以通过runtime给他添加方法的实现原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类的内存空间需求:给系统的NSObject类添加一个name属性
=========NSObject+Dog.m文件===========
#import "NSObject+Dog.h"
#import <objc/runtime.h>
static char const * const dogName = "dogName";
@implementation NSObject (Dog)
- (void)setName:(NSString *)name{
/**
objc_setAssociatedObject 方法共有四个参数 (将某个值跟某个对象关联起来,将某个值存储到某个对象中)
第一个参数 id _Nonnull object: 给哪个对象添加属性
第二个参数 const void * _Nonnull key:属性名称
第三个参数 id _Nullable value:属性值
第四个参数 objc_AssociationPolicy policy:保存策略
*/
objc_setAssociatedObject(self, dogName, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)name{
//objc_getAssociatedObject 获取某个对象的值
return objc_getAssociatedObject(self, dogName);
}
@end
======ViewController.m 文件调用 ======
NSObject * obj = [[NSObject alloc] init];
obj.name = @"哈士奇";
NSLog(@"我就是分类中添加的name属性%@",obj.name);
// 打印结果:
// 我就是分类中添加的name属性哈士奇
暂时整理的这些,后续再补充