美文网首页iOS面试剖析
Runtime相关知识点

Runtime相关知识点

作者: huoshe2019 | 来源:发表于2019-10-08 10:59 被阅读0次
Runtime知识结构

一、数据结构

数据结构

1.1、objc_object

objc_object

我们平常用的实例对象都是id类型,对应到runtime 中的objc_object。

  • isa_t
    共用体
  • 关于isa操作相关
    • 实例对象根据isa指针获取类对象
    • 类对象根据isa指针获取元类对象
  • 弱引用相关
    • 标记一个对象是否曾经有弱引用指针。
  • 关联对象
    • 设置关联属性的一些方法。
  • 内存管理
    • MRC下的retain、release等方法。
    • ARC和MRC下用的autorelease方法。

1.2、objc_class

objc_class

我们平常用的类,对应到runtime中的objc_class。

  • isa_t
    因为继承于objc_object,所以也有isa指针,用来指向类的元类对象。
  • Class superClass
    父类对象
  • cache_t cache
    方法缓存结构<消息传递会有涉及到>
  • class_data_bits_t bits
    bit通过&FAST_DATA_MASK获取class_rw_t,class_rw_t包含方法、属性、协议。

问题1:Class这个类是否是一个对象呢?

解释:
是对象,因为Class对应runtime中的objc_class,objc_class继承于objc_object,所以被称为"类对象"。

1.3、isa指针

1.3.1、isa指针的概念

共用体isa_t

问题2:isa指针的含义

解释:

  • 包含指针型isa非指针型isa
    • 指针型isa:isa的代表Class地址。
    • 非指针型isa:isa的值的部分代表Class地址。
  • 应用场景:
    • 32位架构用指针型的 64位架构用非指针型的。
    • 这是一种提升内存利用的一种手段。
1.3.2、isa指向
  • 关于对象,其指向类对象

    指向类对象
  • 关于类对象,其指向元类对象

    指向元类对象

1.4、cache_t

1.4.1、概念
  • 用于快速查找方法执行函数。
  • 是可增量扩展的哈希表结构。
  • 局部性原理的最佳应用。
1.4.2、结构

cache_t是一个结构体。


cache_t结构

可以理解为:

  • 一个数组中有bucket_t。
  • bucket_t是结构体。
  • bucket_t包含两个成员变量:key、IMP。
  • key就是我们使用的selector。
  • 根据key + 哈希算法,可以快速查找IMP。

1.5、class_data_bits_t

  • class_data_bits_t主要是对class_rw_t的封装。
  • class_rw_t代表了类相关的读写信息、对class_ro_t的封装。
  • class_ro_t代表了类相关的只读信息。

1.5.1、class_rw_t

class_rw_t
  • methods、properties、protocols是runtime动态添加的
    比如:分类中的方法会在运行时添加到methods中。
  • methods、properties、protocols是可读可写的
  • methods、properties、protocols是二维数组
  • methods中存放有method_t对象

1.5.2、class_ro_t

class_ro_t
  • name指的是类名
  • ivars指的是成员变量
  • methods、properties、protocols是编译器内部生成的
  • methods、properties、protocols是只读
  • methods、properties、protocols是一维数组
  • methods中存放有method_t对象

1.5.3、method_t

method_t
  • SEL name
    方法名称
  • const char* types
    返回值 + 参数
  • IMP imp
    无类型的函数指针,指向函数体

1.5.3.1、Type Encodings

Type Encodings
  • 不可变的字符指针
  • 结构:返回值 + n个参数

实例分析:
-(void)aMethod;对应的Type Encodings
解析如下:

aMethod解析
  • aMethod的types === v@:
  • v代表返回值是void无返回值
  • @代表对象id
  • :代表SEl(方法)

注意:

  • 这里的第一个参数和第二个参数是固定的,并且是不可变的。
  • 因为对于OC方法的调用或者说消息传递,到达runtime层面,都会转换成objc_msgSend,它的前面两个参数就是固定的objc_msgSend(obj, sel/@selector(aMethod)/);**。

1.6、整体数据结构

整体数据结构

二、对象、类对象、元类对象

2.1、概念

  • 类对象
    存储实例方法列表等信息。
  • 元类对象
    存储类方法列表等信息。

2.2、isa指针指向图

isa指针指向图

isa指针

  • instance实例对象 --> Class类对象
  • Class类对象 --> meta元类对象
  • meta元类对象 --> meta元类对象
  • meta元类对象 --> meta元类对象(自己)

superclass指针

  • Rootclass类对象 --> nil
  • Superclass类对象 --> Rootclass类对象
  • Subclass类对象 --> Superclass类对象
  • meta元类对象 --> Rootclass类对象

问题3:类对象、元类对象分别是什么,有什么区别和联系?

  • 概念
    类对象是存储实例方法列表等信息。
    元类对象是存储类方法列表等信息。
  • isa指针指向图
    类对象的isa指针指向元类对象
    元类对象的isa指针指向根元类对象
    根元类对象的isa指针指向自己

问题4:如果说调用的一个类方法,没有对应的实现;但是,有同名的实例方法实现,会不会产生崩溃或者实际调用?

  • 由于根meta元类对象superclass指针指向的是根类对象,如果在根元类对象中没有找到对应的类方法,就会去根类对象中去查找同名的实例方法。

问题5:笔试题

笔试题
解释:
  • [self class]转换为objc_msgSend(self, @selector(class))
  • [super class]转换为objc_msgSendSuper(super,@selector(class))
  • [super class]虽然是super调用,但是super是一个结构体,里面包含一个id类型对象receiver,也就是当前对象
  • [self class]传递流程:在缓存中查找,没有➡️在类对象中查找,没有➡️在父类对象中查找,没有➡️在NSObject中查找,有,返回信息。
  • [super class]传递流程:直接在父类对象中查找,没有➡️在NSObject中查找,有,返回信息。

三、消息传递

3.1、函数介绍

void objc_msgSend(void /* id self, SEL op, ... */ )


消息传递

备注:

  • 这里有2个固定参数,一个消息接受者(id类型),一个是方法选择器名称(SEL类型),其它才是后续方法传递的参数。
  • 当前的接受者就是:当前对象

void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )

Super 消息传递

备注:

  • 这里有2个固定参数,一个是结构体类型指针(objc_super),一个是方法选择器名称(SEL类型),其它才是后续方法传递的参数。
  • 因为objc_super内部有一个receiver(id类型),就是当前对象;这个super就是编译器关键字。
  • objc_msgSendSuper:是从父类对象的方法列表中开始查找;
    objc_msgSend:是从当前对象缓存列表中查找。

3.2、消息传递流程

消息传递过程
  • 首先调用方法,查找缓存;如果有,就通过函数指针调用函数,完成方法调用。
    这里的缓存指的是类对象的缓存,是通过哈希查找。

  • 如果缓存中没有对应方法,就会根据当前实例的isa指针去查找类对象的方法列表;如果有,就通过函数指针调用函数,完成方法调用。
    这里分二分查找一般遍历查找

  • 如果当前类方法中没有对应方法,就会逐级父类方法列表中去查找,是通过superclass指针;如果有,就通过函数指针调用函数,完成方法调用。
    这里同样先去缓存查找,再去方法列表查找。

  • 如果直到根类NSObject,都没有查到对应方法,就会进入消息转发流程。

四、消息传递三大步骤详解

4.1、缓存查找

给定值是SEL(方法选择器),目标值是对应的bucket_t中的IMP

缓存查找函数
  • 从缓存查找对应的实现, 是哈希查找。
  • SEL(方法选择器)就是key
  • 利用哈希算法,找到bucket_t,从而找到IMP
  • 哈希查找提高了查找效率。

4.2、当前类中查找

  • 对于已排序好的列表,采用二分查找算法查找方法对应执行函数IMP
  • 对于没有排序的列表,采用一般遍历查找方法对应执行函数。

4.3、父类逐级查找

父类逐级查找
  • 首先根据superclass指针查找有没有父类;没有父类,结束父类查找。
  • 有父类,先去缓存查找;缓存查到了,就返回。
  • 缓存没有查到,就去方法列表查找;方法列表查到了,就返回。
  • 如果上面两步骤都没有查到,就接着查下一个父类。

五、消息转发

消息转发

这里主要讲的是实例方法的转发流程。

5.1、(BOOL)resolveInstanceMethod:(SEL)sel

  • 该方法是类方法
  • 参数是方法选择器(SEL类型)。
  • 返回值是BOOL类型
  • 如果返回值是YES,代表解决了当前实例方法的实现,就结束了消息转发流程;如果返回值是NO,代表没有解决,继续消息转发

5.2、(id)forwardingTargetForSelector:(SEL)aSelector

  • 该方法是实例方法
  • 参数是方法选择器(SEL类型)。
  • 返回值是一个对象,相当于指定的转发目标(自己不处理,让别人处理)。
  • 如果指定了转发目标,就结束了消息转发流程;如果没有指定转发目标,继续消息转发

5.3、(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

  • 该方法是实例方法
  • 参数是方法选择器(SEL类型)。
  • 返回值是一个NSMethodSignature对象,是对一个方法选择器的返回值类型,参数个数以及参数类型的封装。
  • 如果返回了方法签名,则继续调用forwardInvocation;否则就被标记为消息无法处理
    也就是常见的错误:unrecognized selector sent to instance 0x600000da1530

5.4、(void)forwardInvocation:(NSInvocation *)anInvocation

  • 该方法是实例方法
  • 参数是NSInvocation。
  • 无返回值。
  • 如果这里面能够处理这条消息,消息转发流程就结束了。

六、Method-Swizzling

Method-Swizzling

主要代码如下:

//获取test方法
Method test = class_getInstanceMethod(self, @selector(test));
//获取otherTest方法
Method otherTest = class_getInstanceMethod(self, @selector(otherTest));
//交换两个方法的实现
method_exchangeImplementations(test, otherTest);

七、动态添加方法

class_addMethod(<#Class  _Nullable __unsafe_unretained cls#>, <#SEL  _Nonnull name#>, <#IMP  _Nonnull imp#>, <#const char * _Nullable types#>)
  • 第一个参数:为哪个类添加方法。
  • 第二个参数:方法选择器名称(SEL类型)。
  • 第三个参数:函数指针
  • 第四个参数:Type Encodings。

八、动态方法解析

问题6、你是否使用过@dynamic关键字?

解释:
也是借此考察运行时内容,因为使用@dynamic实际上是告诉编译器,自己来实现setter与getter方法,不自动生成。
这样声明的属性,即使你没有实现setter与getter方法,编译时,是不会报错的;但是当你使用到setter与getter方法时(运行时),就会报错。

问题7:编译时语言与运行时语言的区别?

解释:

  • 动态运行时,语言将函数决议推迟到运行时
  • 编译时,语言在编译期进行函数决议

问题8: [obj foo]和obj_msgSend()函数之间有什么关系?

解释:
在经过编译器转换之后,就变成了obj_msgSend(obj,@selector(foo)),然后就开始了消息传递过程。

问题9:runtime如何通过Selector找到对应的IMP地址的?

解释:
考察的是消息传递机制
查找当前实例对应类对象的缓存➡️查找类对象的方法列表➡️逐级查找父级方法列表。

问题10:能否向编译后的类增加实例变量?

解释:
考察的是runtime数据结构。
因为编译后的类,它已经完成了实例变量的布局,这个实例变量是存放在class_ro内部,这个是只读的,所以不能向编译后的类增加实例变量。
但是,可以向动态添加的类中添加实例变量。

问题11:函数调用与消息传递有怎样的区别?

解释:
考察消息传递机制

问题12:当我们调用一个方法没有实现的时候,系统是如何为我们实现消息转发过程的?

解释:
考察消息传递机制

相关文章

  • iOS知识点整理

    iOS知识点整理 持续更新中。。。 runtime相关 iOS 模块详解—「Runtime面试、工作」看我就 ? ...

  • iOS RunTime 详解

    本文讲述 iOS Runtime 相关的知识点,从下面几个方面探寻 iOS Runtime的实现机制。 Runti...

  • Runtime相关知识点

    Objective-C 是一个动态性很强的语言。编译好的内容在运行的时候的可以改变(Runtime的作用)。动态性...

  • Runtime相关知识点

    一、数据结构 1.1、objc_object 我们平常用的实例对象都是id类型,对应到runtime 中的objc...

  • Runtime相关知识点

    各位iOS搬砖工,相信大家在面试的时候经常会遇到Runtime相关的知识点,但是由于日常开发中的确很少用到,所以大...

  • iOS 常见知识点(一):Runtime

    iOS 常见知识点(二):RunLoop iOS 常见知识点(三):Lock Runtime Runtime 是一...

  • iOS面试知识点整理

    1、runtime相关问题 runtime可以说是OC开发中最重要的知识点之一了,面试时回答的完美会让面试官在心底...

  • iOS之RunTime探索与实践

    Runtime 概念 Runtime 相关概念 Runtime 实践 Runtime概念 Runtime简称运行时...

  • IOS面试考察(一):runtime相关问题

    @[TOC] 1. IOS面试考察(一):runtime相关问题 1.1 runtime相关问题 runtime是...

  • 扩展页面间的通信

    参考原文 知识点: runtime.sendMessage runtime.onMessage Chrome 提供...

网友评论

    本文标题:Runtime相关知识点

    本文链接:https://www.haomeiwen.com/subject/kkxructx.html