一:什么是Runtime?
1.Oc是一门动态性比较强的编程语言,和C/C++等语言有着很大的不同,OC的动态性是由Runtime API来支撑的, Runtime API提供的接口信息都是C语言的,源码由C/C++/汇编语言编写的
二:isa详解
在arm64之前,isa就是一个普通的指针,存储着Class,Meta-Class对象的内存地址,在arm64之后,对isa进行了优化,变成了一个共用体union结构,还是用位域来存储更多信息,补充(按位与&运算:都是1才为1,其他都是0,可以取出特定位的二进制值,特定位为1,其他为0,然后去按位与运算;按位或|运算:有1就为1,其他都是0,~按位取反)
位域是指信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。
struct{
char tall :1; 倒数第一位
char rich:1; 倒数第二位
char handsome:1; 倒数第三位
} 占1个字节,不要以为char占1个字节,struct就以为是3个字节,:1位域就代表1个位,总共3个位,但是内存对其导致struct占1个字节->0b0000 0111
三:类对象 Class的结构
struct objc_class {
Class isa;
Class superclass;
cache_t cache;//方法缓存
class_data_bits_t bits;//用于获取具体的类信息
} 当这个bits & FAST_DATA_MASK得到的事
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_list_t *methods; //方法列表
property_list_t *properties;//属性列表
const protocol_list_t *protocols;//协议列表
Class firstSubclass;
Class nextSiblingClass;
char demangledName;
} 而ro
struct class_rw_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
uint32_t reserved;
const uint8_t ivarLayout;
const char *name;//类名
method_list_t *baseMethodList;
property_list_t *baseProtocols;
property_list_t *baseproperties;
const ivar_list_t *ivars;//成员变量列表
const uint8_t *weakIvarLayout;
}
现在我们研究一下这个class_rw_t中的方法列表/属性列表/协议列表是如何存放类对像的方法/属性/协议的,而class_ro_t也有基础方法列表/属性列表/协议列表,两个的区别是什么?
1.class_rw_t里面的methods/properties/protocols是可读可写的二维数组,包含了类的初始内容和分类的内容
2.class_ro_t里面的baseMethodList /baseProtocols/baseProperties/ivars是一维数组,是只读的,包含了类的初始信息
最终方法是放在method_t中,method_t其实是对方法/函数的封装,那么method_t底层是什么呢?
struct method_t {
SEL name;// 函数名
const char *types;//编码(返回值/参数类型)
IMP imp;// 指向函数的指针(函数地址)
}
IMP代表函数的具体实现
typedef id _Nullable (*IMP)(id _Null,SEL _Nunnull,....);
SEL代表方法/函数名,一般叫选择器,底层结构和char 类似
types包含了函数返回至/参数编码的字符串v16@0:8(V代表void返回值,@代表id类型.:代表cmd)
通过isa,找到类对象,在class_rw_t中知道到方法列表.再找到对应的method_t,就可以直接调用函数了,因为method_t里面包含了name函数名,types返回值类型和参数类型,imp指向函数的指针
四:方法缓存
上面可知,类对象中底层有一个cache_t cache,叫方法缓存,用散列表来缓存曾经调用过的函数,可以提高方法的调用速度;主要原理:当调用对象方法时,通过实例对象的isa指针找到类对象,再在类对象的方法列表中找到对应的方法,如果没有就会通过superclass找到父类的类对象,再在其中查找方法,当找到后,会把这个方法缓存到这个cache中,以便下次调用的时候直接读取,调用方法是的sel,如果等于bucket_t中的_key,那么就会通过_imp内存地址找到函数,在调用
struct chache_t{
struct bucket_t *buckets;//散列表 数组,里面缓存这方法
mask_t _mask;// 散列表的长度 - 1;
mask_t _occupied//已经缓存的方法数量
}
struct bucket_t{
cache_key_t _key;//SEL作为key
IMP _imp;//函数的内存地址
}
散列表的具体操作是如何的呢?
调用方法时:objc_msgSend(person,@selector(person));在散列表struct bucket_t *buckets中是如何查找呢?就是用@selector(person)&_mask = 这个方法在散列表中的索引;就会将这个方法缓存到这个索引中,取方法的时候也是这样去取,实际上是牺牲内存空间来换取读取效率;
注:当缓存某一个方法的时候,如果散列表的长度不够的时候,系统会进行扩容处理,但是之前缓存过的方法会清空掉
五:消息调用机制objc_msgSend(receiver对象,SEL函数名称)
OC方法的调用,其实都是转化为objc_msgSend函数的调用,执行流程可以分为3步
1.消息发送
2.动态方法解析
3.消息转发
1.消息发送
先判断是否有消息接受者,如果没有那么直接退出报错,如果有,那么会通过消息接受者的isa指针找到他的receiverClass,在receiverClass的cache方法缓存中查找方法,能够找到,那么调用,如果不能找到,那么会在receiverClass的 class_rw_t *data中查找方法列表(已经排序的二分查找,没有排序的按顺序查找,),找到了那么结束查找并将方法缓存到receiverClass的caches中,如果没有找到,会通过receiverClass的superClass指针找到superClass父类的类对象,先查找superClass的cache_t 方法缓存,能找到,那么结束查找并将方法缓存到receiverClass的caches中,找不到,那么会在superClass的 class_rw_t *data中查找方法列表,找到那么结束查找并将方法缓存到receiverClass的caches中,找不到那么继续查找上一个父类,直到找到,如果还是没找到,那么会进入到第二个阶段:动态方法解析
2.动态方法解析
先判断是否曾经有动态解析,如果是,那么进入第一阶段消息转发阶段的流程;否的话,那么调用
+resolveInstanceMethod:或者是+resolveClassMethod方法来动态解析方法,并且标记为已经动态解析了,然后进入第一阶段的消息发送阶段;如果没有实现这个方法,那么还是标记为已经动态解析,进入消息发送阶段,查找方法,没找到那么进度到第三个阶段:消息转发
+ (Bool)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test)){
Method method = class_getInstanceMethod(self,@selector(other));
class_addMethod(self,sel,method_getImplementrtion(method),method_getTypeEncoding(method));
return YES;
}
return [super resolveInstanceMethod:sel];
} 或者是
void other (id self,SEL _cmd){
NSLog(@"%@-%S--%s",self,sel_getName(_cmd),__func__);
}
+ (Bool)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test)){
class_addMethod(self,sel,(IMP)other,"V16@0:8");
return YES;
}
return [super resolveInstanceMethod:sel];
}
六:消息转发,将消息转发给别人
//将aSelector消息转发给target对象MJCat对象
- (id)forwardingTargetForSeletor:(SEL)aSelector{
if(aSelecor == @selector(test)){
return [[MJCat alloc]init];
}
}//如果这个方法没有实现,那么会调用下面两个方法
//方法签名:返回值类型,参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if(aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封装了一个方法调用,包括:方法调用者/方法名/方法参数
- (void)forwardInvocation:(NSInvocation *)anInvocation{
//anInvocation.target 方法调用者
//anInvocation.selector 方法名
//[anInvocation getArgument: atIndex:];参数等
aninvocation.target = [[MJCat alloc]init];
[anInvocation invoke];//方法调用
}
当来到消息转发的流程后,会调用forwardingTargetForSelector:方法,如果返回值不为空,那么直接objc_msgSend(对象,SEL);如果返回值为nill,调用methodSignatureForSelector:方法,返回值不为空,那么调用forwardInvocation:方法,如果返回值为nill,调用doesNotRecognizeSelector:方法
七:super的本质
在CJStudent中实现init方法, CJStudent继承至CJPerson
- (instancetype)init{
if(self == [super init]){
NSLog(@"%@",[self class]);//CJStudent
NSLog(@"%@",[self superclass]);//CJPerson
NSLog(@"%@",[super class]);//CJStudent
NSLog(@"%@",[super superclass]);//CJPerson
}
}
为什么呢?[super class]本质上是调用的objc_msgSendSuper方法, objc_msgSendSuper(struct objc_super{__unsafe_unretained _Nunnull id receiver;__unsafe_unretained _Nunnull Class super_class},@selector(class)); objc_super是一个结构体, receiver成员代表消息接受者, super_class代表消息接受者的父类,作用是在父类中查找该方法的实现;实际上objc_msgSendSuper({self,[CJPerson class]},@selector(class));就是说在父类中查找class的实现,但是class返回的是谁调用,谁是消息调用者,就返回谁的类对象, superclass原理一样
八:-isMemberOfclass:和+isMemberOfclass:和-isKindOfClass:和+isKindOfClass:的区别
-isMemberOfclass:方法调用者的class时候就是传进去的class
-isKindOfClass:方法调用者的class时候就是传进去的class或者是方法调用者的superclass就是传进去的class,类方法以此类推
九:什么是runtime?项目中的作用有哪些?
OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时在进行,OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性的函数,平时编写的OC代码,底层都是转换成对Runtime的API调用
1.关联对象(associatedObject)给分类添加属性
2.遍历类的所有成员变量(修改testFiled的占位文字颜色,字典转模型,接档归档)
3.交换方法实现。。。。