runtime iOS 运行时
记录一下runtime的知识点,一些是从其他文章学到的,一些是自己开发过程中的经验,加上了一些自己的理解。很多地方可能不是非常准确,但是我觉得在实际开发或者面试这么去理解也是可以的。
什么是runtime:
1.本质:runtime是一个c语言和编译语言编写的库。
2.作用:学OC的时候老师傅肯定都说过OC是由C语言为基础的,runtime就是实现C到OC的核心。它实现了OC语言的面相对象和动态消息传递机制。
很多不严谨的时候runtime,运行时,动态特性和消息传递机制(或消息转发消息分发)这几个词,会在同一个语境下使用。关于动态特性或消息传递机制在后面会有解释。
runtime有什么用:
一个基本的用法:
id oneModel = [[UIViewController alloc]init];
//1
objc_msgSend(oneModel, @selector(loadView));
//2
objc_msgSend(oneModel, NSSelectorFromString(@"loadView"));
//3
[oneModel loadView];
1,2,3这三行代码作用是一样的,都是同一个实例对象调用同一个方法。
如果是第一次接触runtime。之前只是用oc来开发的话,应该会想到很多用法。之前我们局限于:获取某个类或对象,调用这个类或对象的方法。现在我们可以通过一段字符串,来获取到类或者对象,并且通过字符串让其执行方法,甚至我们可以通过接口返回值直接调用方法:
...
...
//接口返回的(str = ???
//之前:
if([str isEqualToString:@"loadView"] )
{
[self loadView];
}
//现在:
objc_msgSend(self, NSSelectorFromString(str));
我当初第一次接触runtime,脑子里突然出现了很多危险的想法。🙊🙊🙊,
isa指针,基类,元类:
说这个之前需要先说一下几个概念:
isa指针:一个对象的isa指针指向创建它的类。
元类:在oc中,类其实也是一种实例对象,也有isa指针,类的isa指针指向它的元类。
基类:就是父类的父类的父类。。。OC里面可以看做是NSObject。
基类元类:元类也是有isa指针的,元类的isa指针指向基类的元类,即NSObject的元类,最终基类的元类的isa指针指向自身。
我们app运行的时候工作的规则:元类和类对象,都是使用时才会被创建,并且程序运行期间只会被创建一次,并且知道程序被杀死才被释放,所以同一种类型的实例对象的isa指针,应该是都指向一个地址的。所以如果在app运行的时候查看内存的占用,直观表现应该是第一次进行的一些操作会导致内存占用大于之后多次重复的操作,因为第一次进行的操作很大概率有新的类对象初始化,类对象生成实例对象,然后新的类对象会被创建并保存,不会被释放。
NSObject结构体
看一下NSObject结构体的样子:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY; //isa指针
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE; //父类
const char * _Nonnull name OBJC2_UNAVAILABLE; //类名
long version OBJC2_UNAVAILABLE; //版本,
long info OBJC2_UNAVAILABLE; //类信息,
long instance_size OBJC2_UNAVAILABLE; //实例变量大小
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;//成员变量链表
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; //方法定义链表
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; //方法缓存
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; //协议链表
#endif
} OBJC2_UNAVAILABLE;
其中:isa指针,super_class, name,比较好理解,
方法链表
objc_method_list和objc_cache两个存储方法的链表:
objc_method结构体和objc_method_list:
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;//方法返回类型
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
struct objc_method_list {
struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
SEL:
表示一个selector的指针。系统在编译过程中,会根据方法的名字以及参数序列生成一个用来区分这个方法的唯一ID编号,这个 ID 就是SEL类型的。本质上就是一个保存方法名的字符串
SEL aSel = @selector(btnClicked:);//生成一个SEL实例,
//按钮添加方法:
[btn addTarget:self action:@selector(btnClicked:) forControlEvents:UIControlEventTouchUpInside];
//可以点击看一下这个action参数,就是SEL类型
//以字符串创建:
SEL a_sel = NSSelectorFromString(@"didReceiveMemoryWarning");
IMP:
IMP为指向函数实现的指针,
关于method 传送门: https://www.jianshu.com/p/50054548013a
SEL aSel = @selector(didReceiveMemoryWarning);
Method method = class_getInstanceMethod([self class], aSel);
IMP imp = method_getImplementation(method);
((void (*) (id, SEL)) (void *)imp)(self, aSel);
//通过Method获取IMP
//IMP method_getImplementation(Method m);
// 返回方法的具体实现
//IMP class_getMethodImplementation ( Class cls, SEL name );
//IMP class_getMethodImplementation_stret ( Class cls, SEL name );
消息转发
当我们调用方法的时候,会优先从objc_cache中寻找此方法,若找不到,则会去objc_method_list中寻找。而在objc_method_list找到之后,objc_cache缓存此方法。
如果在objc_method_list里也没找到,就通过super_class去其父类寻找。一直找到基类还没有,就抛出异常。另外还可以自定义消息转发失败之后的处理方法。
关于消息转发传送门:https://www.jianshu.com/p/45db86af7b60
======================
======================
======================
======================