iOS 面试第十节 Runtime

1.消息机制

OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)

objc_msgSend底层有3大阶段,消息发送(当前类、父类中查找)、动态方法解析、消息转发

objc_msgSend做了如下事情:

  1. 通过对象的isa指针获取类的结构体。
  2. 在结构体的方法表里查找方法的selector。
  3. 如果没有找到selector,则通过objc_msgSend结构体中指向父类的指针找到父类,并在父类的方法表里查找方法的selector。
  4. 依次会一直找到NSObject。
  5. 一旦找到selector,就会获取到方法实现IMP。
  6. 传入相应的参数来执行方法的具体实现。
  7. 如果最终没有定位到selector,就会走消息转发流程。

当对象无法接收消息,就会启动消息转发机制,通过这一机制,告诉对象如何处理未知的消息。

对象接收到未知的消息时,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或 者+resolveClassMethod:(类方法)。

在这个方法中,我们有机会为该未知消息新增一个”处理方法”。使用该“处理方法”的前提是已经实现,只需要在运行时通过class_addMethod函数,动态的添加到类里面就可以了。

  1. 我们可以在此对类进行一个动态方法添加

  2. 备用接收者
    如果在上一步无法处理消息,则Runtime会继续调下面的方法。
    -(id)forwardingTargetForSelector:(SEL)aSelector;(此后消息只考虑对象方法了)
    如果这个方法返回一个对象,则这个对象会作为消息的新接收者。注意这个对象不能是self自身,否则就是出现无限循环。如果没有指定对象来处理aSelector,则应该 return [super forwardingTargetForSelector:aSelector]。

第三部:Normal forwarding 常规转发阶段

如果第2步返回self或者nil,则说明没有可以响应的目标 则进入第三步。
第三步的消息转发机制本质上跟第二步是一样的都是切换接受消息的对象,但是第三步切换响应目标更复杂一些,第二步里面只需返回一个可以响应的对象就可以了,第三步还需要手动将响应方法切换给备用响应对象。
-(void)forwardInvocation:(NSInvocation *)anInvocation;
第三步有2个步骤:

a. 定位可以响应封装在anInvocation中的消息的对象。
b. 使用anInvocation作为参数,将消息发送到选中的对象。anInvocation将会保留调用结果,runtime会提取这一结果并发送到消息的原始发送者。

可以简单理解forwardInvocation为:
forwardInvocation:方法可以作为无法认识的消息的分发中心,将它们打包发给不同的接受者,或者作为一个中转站将所有消息发往同一个目的地。它可以将一个消息转换成另外的一个消息,或者只是单纯的把消息“吞下”,不对外响应和抛出错误

总结:
在三个步骤的每一步,消息接受者都还有机会去处理消息。同时,越往后面处理代价越高,最好的情况是在第一步就处理消息,这样runtime会在处理完后缓存结果,下回再发送同样消息的时候,可以提高处理效率。第二步转移消息的接受者也比进入转发流程的代价要小,如果到最后一步forwardInvocation的话,就需要处理完整的NSInvocation对象了。

动态方法解析
add_class

转发机制
在这里插入图片描述

从全局来看,消息转发机制共分为3大步骤:
1.Method resolution 方法解析处理阶段
2.Fast forwarding 快速转发阶段
3.Normal forwarding 常规转发阶段

2.Category 的实现原理?

  • Category 实际上是 Category_t的结构体,在运行时,新添加的方法,都被以倒序插入到原有方法列表的最前面,所以不同的Category,添加了同一个方法,执行的实际上是最后一个。

  • Category 在刚刚编译完的时候,和原来的类是分开的,只有在程序运行起来后,通过 Runtime ,Category 和原来的类才会合并到一起。

3.isa指针的理解,对象的isa指针指向哪里?isa指针有哪两种类型?

  • isa 等价于 is kind of
    实例对象的 isa 指向类对象
    类对象的 isa 指向元类对象
    元类对象的 isa 指向元类的基类
  • isa 有两种类型
    纯指针,指向内存地址
    NON_POINTER_ISA,除了内存地址,还存有一些其他信息

4.多线程的 并行 和 并发 有什么区别?

并行是同时处理多个任务
并发是同一个时间段内处理多个任务。
由于一个CPU只能处理一个线程,所以当前cpu如果是单核时候,其实只能进行并发,也就是短时间内多线程来回切换。当cpu是多核的时候,才有可能是并行处理多个任务。

5.Objective-C 如何实现多重继承?

Object-c的类没有多继承,只支持单继承,如果要实现多继承的话,可使用如下几种方式间接实现

  • 通过组合实现
    A和B组合,作为C类的组件。也就是我们常说的在C类中导入A类、B类然后实例化他们执行他们对应的方法。

  • 通过协议实现
    C类实现A和B类的协议方法

  • 消息转发实现
    forwardInvocation:方法 <我们可以把这个方法看成消息中转站,在这里我们可以把当前对象无法处理的消息,进行包装分配给其它对象。也可以对这个消息进行替换,然后再发给其它对象。当然我们也可以选择自己吃了这个方法不进行任何操作,以避免崩溃>

6.runtime 如何实现 weak 属性?

weak 此特质表明该属性定义了一种「非拥有关系」(nonowning relationship)。为这种属性设置新值时,设置方法既不持有新值(新指向的对象),也不释放旧值(原来指向的对象)。

runtime 对注册的类,会进行内存布局,从一个粗粒度的概念上来讲,这时候会有一个 hash 表,这是一个全局表,表中是用 weak 指向的对象内存地址作为 key,用所有指向该对象的 weak 指针作为 value。当此对象的引用计数为 0 的时候会 dealloc,假如该对象内存地址是 a,那么就会以 a 为 key,在这个 weak 表中搜索,找到所有以 a 为键的 weak 对象,从而设置为 nil。

runtime 如何实现 weak 属性具体流程大致分为 3 步:

1、初始化时:runtime 会调用 objc_initWeak 函数,初始化一个新的 weak 指针指向对象的地址。

2、添加引用时:objc_initWeak 函数会调用 objc_storeWeak() 函数,objc_storeWeak() 的作用是更新指针指向(指针可能原来指向着其他对象,这时候需要将该 weak 指针与旧对象解除绑定,会调用到 weak_unregister_no_lock),如果指针指向的新对象非空,则创建对应的弱引用表,将 weak 指针与新对象进行绑定,会调用到 weak_register_no_lock。在这个过程中,为了防止多线程中竞争冲突,会有一些锁的操作。

3、释放时:调用 clearDeallocating 函数,clearDeallocating 函数首先根据对象地址获取所有 weak 指针地址的数组,然后遍历这个数组把其中的数据设为 nil,最后把这个 entry 从 weak 表中删除,最后清理对象的记录。

7.讲一下 OC 的消息机制

  • OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
  • objc_msgSend底层有3大阶段,消息发送(当前类、父类中查找)、动态方法解析、消息转发

8.runtime具体应用

  • 利用关联对象(AssociatedObject)给分类添加属性
  • 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
  • 交换方法实现(交换系统的方法)
  • 利用消息转发机制解决方法找不到的异常问题
  • KVC 字典转模型(1.可以model类中把automaticallyNotifiesObserversForKey设置为NO就可以解决 服务器带有字段,model没有字段而setValue forke方法所导致的crash。因为kvc原理是在set方法找找不到_对象找都找不到在undefxxxx找。这个开关关了就只会执行set方法了。2.当然我们也可以在model类中重写unxxx这个方法防治crash)

8.runtime如何通过selector找到对应的IMP地址?

每一个类对象中都一个对象方法列表(对象方法缓存)

  • 类方法列表是存放在类对象中isa指针指向的元类对象中(类方法缓存)。
  • 方法列表中每个方法结构体中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现。
  • 当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类对象方法列表里查找。
  • 当我们发送一个消息给一个类时,这条消息会在类的Meta Class对象的方法列表里查找。

9.简述下Objective-C中调用方法的过程

Objective-C是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector),整个过程介绍如下:

  • objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类
  • 然后在该类中的方法列表以及其父类方法列表中寻找方法运行
  • 如果,在最顶层的父类(一般也就NSObject)中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX
    但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会,这三次拯救程序奔溃的说明见问题《什么时候会报unrecognized selector的异常》中的说明。

10.load和initialize的区别

两者都会自动调用父类的,不需要super操作,且仅会调用一次(不包括外部显示调用).

  • load和initialize方法都会在实例化对象之前调用,以main函数为分水岭,前者在main函数之前调用,后者在之后调用。这两个方法会被自动调用,不能手动调用它们。
  • load和initialize方法都不用显示的调用父类的方法而是自动调用,即使子类没有initialize方法也会调用父类的方法,而load方法则不会调用父类。
  • load方法通常用来进行Method Swizzle,initialize方法一般用于初始化全局变量或静态变量。
  • load和initialize方法内部使用了锁,因此它们是线程安全的。实现时要尽可能保持简单,避免阻塞线程,不要再使用锁。

10.怎么理解Objective-C是动态运行时语言。

  • 主要是将数据类型的确定由编译时,推迟到了运行时。这个问题其实浅涉及到两个概念,运行时和多态。
  • 简单来说, 运行时机制使我们直到运行时才去决定一个对象的类别,以及调用该类别对象指定方法。
  • 多态:不同对象以自己的方式响应相同的消息的能力叫做多态。
  • 意思就是假设生物类(life)都拥有一个相同的方法-eat;那人类属于生物,猪也属于生物,都继承了life后,实现各自的eat,但是调用是我们只需调用各自的eat方法。也就是不同的对象以自己的方式响应了相同的消 息(响应了eat这个选择器)。因此也可以说,运行时机制是多态的基础.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值