iOS 常见知识点(一):Runtime:https://www.jianshu.com/p/965bd18cb056
https://www.jianshu.com/p/adf0d566c887
iOS开发-runtime-消息传递和转发机制:http://zhangzr.cn/2018/02/08/iOS%E5%BC%80%E5%8F%91-runtime-%E6%B6%88%E6%81%AF%E4%BC%A0%E9%80%92%E5%92%8C%E8%BD%AC%E5%8F%91%E6%9C%BA%E5%88%B6/
iOS运行时(Runtime)详解+Demo:https://www.jianshu.com/p/adf0d566c887
1.什么是Runtime
- runtime是运行时,是OC的运行机制,最重要的是消息机制
- 对于C语言,函数的调用在编译时,就会决定调用哪个函数
- 对于OC,属于动态调用过程,编译的时候不能决定调用哪个函数,只有在真正运行的时候才会找到对应的函数
- 在编译的阶段,OC可以调用任何函数,只要声明就不会报错,而C语言未实现的函数编译时就会报错
- 编译器的工作:把OC代码编译成底层实现,xcode编译器clang
clang -rewrite-objc main.m
2.runtime怎么使用
1.先导入头文件
#import <objc/message.h>
2.再调用runtime的方法
//普通方法创建对象p
Person *p = [[Person alloc]init];
//runtime创建对象p
Person *p = objc_msgSend(objc_getClass("Person"),sel_registerName("alloc")); // Person = [p alloc];
p = objc_msgSend(p,sel_registerName("init")); // p = [p init];
//调用不带参数的方法
objc_msgSend(p, @selector(eat) ); //[p eat];
//调用带参数的方法
objc_msgSend(p, @selector(run) , 20); //[p run:20];
3.什么时候用runtime
- 装逼
- 可以调用私有的方法,系统的方法
- 交换方法
4. 方法调用的流程(消息机制)
须知:
对象方法,保存在对应的类的方法列表中
类方法,保存在对应元类的(meta class)方法列表中
以[p eat];
为例
- 寻找p对应的类,即Person类。每个对象都有一个isa指针,指针指向它对应的类,再去类的方法列表中查找对应方法。在
[p eat];
中,p
的isa
指针指向了Person
类(p.isa -> Person
) - 注册一个方法编号,
sel_registerName("eat")
(操作数字比操作字符串快) - 根据方法编号查找对应方法,(在
[p eat];
中,再去Person
类中的方法列表(MethodList
)中寻找对应方法) - 根据方法地址去方法区调方法的实现
5.利用runtime交换方法
需求:改造[UIImage imageNamed:@"1.png"]; 方法,可以检测是否成功加载图片。
方法一:自定义UIImage,重写[UIImage imageNamed:]方法。
- 弊端:1. 每次使用都需要#import自己的类。2. 项目大了,不好一个个去修改
#import "CYBImage.h"
@implementation CYBImage
+(UIImage *)imageNamed:(NSString *)name{
UIImage *image = [super imageNamed:name];
if (image){
NSLog(@"加载成功");
}else{
NSLog(@"加载失败");
}
return image;
}
@end
#import "CYBImage.h"
CYBImage *image = [CYBImage imageNamed:@"1.png"];
//2020-09-25 10:49:30.397798+0800 [54068:2433122] 加载失败
方法二:给UIImage添加分类
- 弊端:1. 重写系统方法,会覆盖系统的方法 2. 分类中无法调用super
对UIImage添加Category分类,super ——>指向父类(NSObject),父类NSObject中无法使用
[ UIImage imageNamed:]
这个方法
方法三:使用runtime修改系统的方法
想要修改系统的自带类,只能用runtime解决(交换方法)。
步骤:
- 给系统的方法添加分类
- 添加一个带有扩展功能的方法
#import "UIImage+image.h"
@implementation UIImage (image)
+(UIImage *)cyb_imageNamed:(NSString *)name{
//1.加载图片
UIImage *image = [UIImage imageNamed:name];
//2.判断加载成功
if(image) {
NSLog(@"cyb_imageNamed加载成功");
}else {
NSLog(@"cyb_imageNamed加载失败");
}
return image;
}
@end
- 交换两个方法的实现,只需要交换一次
//把类加载进内存的时候调用,只会调用一次,swift里面没有+load
+(void)load
{
//获取imageNamed
//参数1:获取哪个类
//参数2:获取这个类的哪个方法
Method imageNamed = class_getClassMethod(self, @selector(imageNamed:));
//获取cyb_imageNamed
Method cyb_imageNamed = class_getClassMethod(self, @selector(cyb_imageNamed:));
//交换方法
method_exchangeImplementations(imageNamed, cyb_imageNamed);
}
//会调用多次,swift可以用这种方法
+(void)initialize
{
//dispatch_once可以保证只调用一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
}
- 交换使用到
imageNamed:
和cyb_imageNamed:
的地方
+(UIImage *)cyb_imageNamed:(NSString *)name{
-----------------------这里交换了使用的方法------------------------
UIImage *image = [UIImage cyb_imageNamed:name];
-----------------------这里交换了使用的方法------------------------
if(image) {
NSLog(@"cyb_imageNamed加载成功");
}else {
NSLog(@"cyb_imageNamed加载失败");
}
return image;
}
- 测试是否交换成功
#import "UIImage+image.h"
UIImage *image = [UIImage imageNamed:@"1.png"];
//2020-09-25 11:16:56.735535+0800 [54357:2444466] cyb_imageNamed加载失败
图解方法交换
6. 用Runtime动态添加方法
- OC都是懒加载机制,只要有一个方法实现了,就会马上添加到方法列表中
- 当我们调用了一个没有实现的方法,编译时不会报错,但运行时会报错,这时,我们可以让OC动态地在运行时添加方法
[p performSelector: @selector(eat)];
#import <objc/message.h>
//当调用了一个没有实现的方法时,调用这个方法
+(BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == NSSelectorFromString(@"eat")){
//class:给哪个类添加方法
//SEL:添加哪个方法
//IMP:方法的实现
//type:方法的类型(v = void ,@ = id , : = SEL)
class_addMethod(self,sel,(IMP) eat,"v@:");
}
}
7 动态添加属性
- 添加属性的本质:让某个属性与某个对象产生关联,即属性的指针指向这个对象。
需求:给NSObject添加属性name。
步骤
- 给NSObject添加分类
- 在分类中声明属性(分类中添加属性,只会声明set和get方法,不会生成实现,也不会生成下划线的成员属性)
- 在分类中添加set和get方法,可以使用全局静态变量(static)来保存属性。
- 用runtime添加属性
static NSString* _name;
//给NSObject类添加一个name属性,设置它的set和get方法
-(void)setName:(NSString *)name{
_name = name
//第一个参数:给哪个类添加属性
//第二个参数:属性的名称
//第三个参数:属性值
//第四个参数:保存策略
objc_setAssociateObject(self,@"name",name,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)name{
return _name
//第一个参数:获得哪个类添加属性
//第二个参数:属性的名称
return objc_getAssociateObject(self,@"name");
}