iOS面试题(十三)Runtime --对象、类对象、元类对象&消息传递机制

4.动态运行时(RunTime)

  • 基础数据结构
  • 对象、类对象、元类对象是怎么理解的?以及他们之间的关系是怎样的(实例与类对象之间的关系以及类对象与元类对象之间的关系)?
  • OC语言中的消息传递机制是怎样的?
  • 方法缓存  (我们进行方法查找的过程当中,如何进行缓存的方法查找?会使用到系统或者说Runtime中一个方法缓存的机制,这个机制又是怎样运行的呢?)
  • 消息转发流程是怎样的?
  • Method-Swizzling(方法混写)是Runtime的一种运用(我们可以再运行时,去替换一些方法的实现,也得益于动态运行时这一个特性),可能会结合实际的场景,比如说通过时长统计框架这样的考察来了解对于Runtime的Methood-Swizzling技术的一个实际的运用
  • 动态添加方法(Runtime提供的功能实现)
  • 动态方法解析(Runtime提供的功能实现)
     

对象、类对象、元类对象类对象、元类对象分别是什么,他们的区别是什么?)

  • 类对象是存储实例方法列表等信息的结构,实例对象可以通过isa指针找到自己的类对象,来访问实例方法
  • 元类对象存储类方法列表等信息的内容, 类对象可以通过isa找到自己的元类对象,来访问类方法

红色箭头代表superClass指向,黑色虚线表示isa指针的指向

  • Root class代表根类,父类指向nil,可以理解为NSObject 
    对应这个类对象,有子类Superclass(class),以及子类的子类Subclass(class)
  • 当我们给定一个实例的时候,左边的实例(instance of Subclass)是id类型的,也就是objc_object数据结构,内含isa成员变量指针,指向这个实例所对应的类对象 (例如有个类叫ClassA,创建出一个instanceA,它的isa指针就指向classA)
    所以:实例和类对象的关系:实例(instance)通过isa指针可以找到自己的类对象(class
  • 中间那行类对象是objc_class结构, 最右侧(Superclass)分别为子类,父类和根类的元类对象(元类对象之间也是继承关系)
  • 类对象class也有isa指针,指向元类对象(Superclass meta)
  • 元类对象因为也是objc_class类型,所以也有isa指针。对于任何一个元类对象,包括根元类对象自身,它的isa指针都指向根元类对象
  • 根元类对象的superClass指针指向它的根类对象

类对象和元类对象的区别和联系:

  1. 实例(instance)通过isa指针可以找到自己的类对象(class),类对象当中存储方法列表等信息
  2. 以及类对象可以通过isa指针找到它的元类对象(Superclass meta),从而可以访问一些关于类方法列表的相关信息。
  3. 类对象和元类对象都是objc_class数据结构的,因为objc_class继承了objc_object,所以它们才有isa指针,进而才能实现我们刚才所描述的操作。

 

如果我们调用了一个类方法,会从元类的方法列表中查找,查找的过程是沿着红色箭头指向逐级查找
当我们调用的类方法,在元类当中找不到的时候,就会找根类对象当中同名的实例方法实现

所以,如果我们调用一个类方法,没有对应的实现,但是有同名的实例方法实现,会不会发生崩溃?会不会产生实际的调用?

  • 会产生实际调用,原因是根元类对象的superClass指针指向了根类对象
  • 所以在元类中查找不到类方法的时候,就会顺着superClass指针去实例方法列表中查找
  • 若有同名方法,就会实现调用
     

简单的描述下消息传递的过程:

  • 假如我们调用了实例方法A(instance),系统会根据当前实例的isa指针,找到它的类对象。
    在它的类对象中遍历方法列表,若没找到
    会顺着SubClass指针指向去父类(SuperClass)类对象方法列表当中查找
    一直到根类(RootClass)对象的方法列表查找,如果没找到就会进入消息转发流程
  • 如果调用的是类方法(SubClass),系统会通过类对象的isa指针去原来对象中查找,顺次遍历方法列表
    直到根元类(Rootclass meta)对象
    然后再到根类对象(RootClass),都没有的话,进入消息转发流程
  • 遍历类方法和实例方法的区别是:在根元类的指向根元类的super会指向根类对象

OC语言中的消息传递机制是怎样的?

  •  objc_msgSend函数固定要接受两个参数,id类型的self和SEL类型的方法选择器名称
     再后面才是消息传递的真正的方法参数。
  • 对于任何一个消息传递[self Class],通过编译器都会转换成关于objc_msgSend的函数调用
    第一个参数是消息传递的接收者self ,第二个参数是我们传递的消息名称,也叫做选择器
  • 对于消息传递,在编译器层面,其实转换成了函数调用
    [self Class] ---- objc_msgSend(self,@selector(class))

另一个函数:

  • 也有两个固定参数, super结构体类型的指针和方法选择器(SEL op),后续是实际参数(...)
  • super结构体中包含了receiver成员变量,这个接收者就是当前对象
    因为super是编译器关键字,经过编译器编译之后,会解析成objc_super结构体指针
    而结构体中的成员变量receiver,就是当前对象
  • 不论是调用self class还是super class ,这条消息的接收者都是当前对象

那么self class和super class有什么区别呢? 

 

  • 在调用一个方法的时候,先会查找缓存,看缓存中是否有对应选择器名称的方法。
  • 如果有,就通过函数指针调用函数,就完成了消息传递。
  • 如果缓存没有,就会根据当前实例的isa指针,去查找当前类对象的方法列表
            如果找到同样名称的方法,就通过函数指针调用这个函数实现,结束这次消息传递
            如果当前类方法中没有,通过当前类对象的superClass指针去就逐级父类方法列表查找。
            若父类也没有,就根据父类的superClass指针往上查找,直到nil,若都没有,就进入消息转发流程

总结:首先查缓存,没命中查当前类的方法列表,没命中顺次查找它的各个父类方法列表,都没命中转到消息转发流程。下个章节方法缓存再做补充

具体缓存查找流程

根据给定的方法选择器(SEL),来查找(bucket_t)中对应的方法实现(IMP)

  1. 缓存是否命中,缓存查找是哈希查找
  2. 当前类方法列表是否命中,已排序好的是二分查找,未排序好的是一般查找
  3. 逐级父类方法列表是否命中,根据superClass指针逐级查找父类,在父类中也是先查找缓存,再查找父类

看下这个笔试题:


 

id objc_msgSend(id self, SEL op, ...);
/*
返回值是一个id,objc_msgSend发送消息的本质就是,
第一个参数self就是当前响应消息的对象指针,记录这个对象的地址
SEL类型 类似C语言中的函数指针,记录方法列表。在OC里面就是方法名。
*/
 NSLog((NSString *)&__NSConstantStringImpl__var_folders_cz_l7gn4kjj52x1y4xtvjjvy_l40000gn_T_Student_28e7c0_mi_0,
NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
  • 对于[self class]来说,首先会被转化成objc_msgSend函数调用
    [super class]会被转化成objc_msgSendSuper函数调用,objc_msgSendSuper的参数虽然是super,
    但super结构体里面包的receiver是当前对象,所以这两个方法的接收者都是当前对象
  • 假如现在Phone,实例在8,初始化的时候,我们通过[self class]打印类信息
    会通过8的isa指针找到Phone的类对象14在这里寻找class方法,本身是没有的
    然后会通过superClass指针向上找,找到6号Mobile父类,他这里也没有class实现
    一直找到根类对象5号,也就是NSObject,有class实现,就会调用class具体实现
    返回给调用方,打印出来的就是Phone
id objc_msgSendSuper(struct objc_super *super, SEL op, ...);
/*
 struct objc_super *super  结构体指针类型。-->struct objc_super{id receiver;Class super_class;}    receiver:记录消息的接收者  super_class:表示super表示的父类是什么。super是调用父类的方法的。
  
*/
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cz_l7gn4kjj52x1y4xtvjjvy_l40000gn_T_Student_28e7c0_mi_1,
NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super)
{(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("class"))));
  • 对于[super Class],实际的接收者仍然是Phone这个实例,调用了objc_msgSendSuper
    也就是从父类的方法列表6开始查找,跨越了当前Phone的类方法列表14号,
    但6号也没有Class方法,一直找到根类NSObject有Class,
    接收者仍然是当前对象(id)self,返回的仍然是Phone
  • 所以此面试题打印结果两个都是Phone。


 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值