iOS Runtime经典面试题整理

1,runtime 如何动态添加方法和属性和动态属性控制

通过苹果官方文档查看,我们可以发现runtime的内部存在很多方法,


图片.png

ivar表示成员变量
class_addIvar
class_addMethod
class_addProperty
class_addProtocol
class_replaceProperty

runtime通过这个几个方法就能动态的控制添加方法,添加属性,添加协议以及替换属性等,

例如 动态变量控制

在程序中,xiaoming的age是10,后来被runtime变成了20,来看看runtime是怎么做到的。

1.动态获取XiaoMing类中的所有属性[当然包括私有]

Ivar *ivar = class_copyIvarList([self.xiaoming class], &count);

2.遍历属性找到对应name字段

const char *varName = ivar_getName(var);

3.修改对应的字段值成20

object_setIvar(self.xiaoMing, var, @"20");

2,runtime 如何实现 weak 属性

weak属性的特点:

weak 表明该属性定义了一种“非拥有关系” (nonowning relationship)。
为weak属性设置新值时,设置方法既不保留新值,也不释放旧值。
同assign类似,然而在属性所指的对象释放时候,属性值也会清空(nil out)。

runtime是如何实现 weak 变量的自动置nil?

weak 对象会放入一个 hash 表中。

用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc。
假如 weak 指向的对象内存地址是addr,那么就会以addr为键, 在这个 weak 表中搜索,找到所有以addr为键的 weak 对象,从而设置为 nil。

具体实现机制:

objc_storeWeak(&weakPo, Model)函数:

objc_storeWeak函数把赋值对象(Model)的内存地址作为键值key,将weak修饰的属性变量(weakPo)的内存地址(& weakPo)作为value,注册到 weak 表中。

如果Model为0(nil),那么把变量(weakPo)的内存地址(& weakPo)从weak表中删除,

可以把objc_storeWeak(&weakPo, Model)理解为:objc_storeWeak(value, key),并且当key变nil,将value置nil。

在Model非nil时,weakPo和Model指向同一个内存地址,在Model变nil时,weakPo变nil。此时向weakPo发送消息不会崩溃:在Objective-C中向nil发送消息是安全的。

3,weak属性需要在dealloc中置nil么

在ARC环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们处理
即便是编译器不帮我们做这些,weak也不需要在dealloc中置nil
在属性所指的对象遭到摧毁时,属性值也会清空

4,runtime如何通过selector 找到对应的 IMP 地址(分别考虑类方法和实例方法)

对象中有类方法和实例方法的列表,列表中记录着方法的名词、参数和实现,而selector本质就是方法名称,runtime通过这个方法名称就可以在列表中找到该方法对应的实现。

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        
    const char *name                                         
    long version                                             
    long info                                                
    long instance_size                                       
    struct objc_ivar_list *ivars                             
    struct objc_method_list **methodLists                    
    struct objc_cache *cache                                 
    struct objc_protocol_list *protocols                     
#endif

} OBJC2_UNAVAILABLE;

这里声明了一个指向struct objc_method_list指针的指针,可以包含类方法列表和实例方法列表

具体实现

在寻找IMP的地址时,runtime提供了两种方法

IMP class_getMethodImplementation(Class cls, SEL name);
IMP method_getImplementation(Method m)

第一种方法

对于第一种方法而言,类方法和实例方法实际上都是通过调用class_getMethodImplementation()来寻找IMP地址的,不同之处在于传入的第一个参数不同。
类方法(假设有一个类 A)

class_getMethodImplementation(objc_getMetaClass("A"),@selector(methodName));

实例方法

class_getMethodImplementation([A class],@selector(methodName));

通过该传入的参数不同,找到不同的方法列表,方法列表中保存着下面方法的结构体,结构体中包含这方法的实现,selector本质就是方法的名称,通过该方法名称,即可在结构体中找到相应的实现。

struct objc_method {
    SEL method_name                                      
    char *method_types                                       
    IMP method_imp                                           
}

第二种方法

传入的参数只有method,区分类方法和实例方法在于封装method的函数
类方法

Method class_getClassMethod(Class cls, SEL name)

实例方法

Method class_getInstanceMethod(Class cls, SEL name)

最后调用IMP method_getImplementation(Method m) 获取IMP地址

5,使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?

无论在MRC下还是ARC下均不需要,被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被 NSObject -dealloc 调用的object_dispose()方法中释放。
NSObject 调 -dealloc
只做一件事:调用 Objective-C runtime 中的 object_dispose() 方法 调用 object_dispose()
*为 C++ 的实例变量们(iVars)调用 destructors
*为 ARC 状态下的 实例变量们(iVars) 调用 -release

  • 解除所有使用 runtime Associate方法关联的对象
  • 解除所有 __weak 引用
    *调用 free()
1、调用 -release :引用计数变为零
对象正在被销毁,生命周期即将结束. 
不能再有新的 __weak 弱引用,否则将指向 nil.
调用 [self dealloc]

2、 父类调用 -dealloc 
继承关系中最直接继承的父类再调用 -dealloc 
如果是 MRC 代码 则会手动释放实例变量们(iVars)
继承关系中每一层的父类 都再调用 -dealloc

>3、NSObject 调 -dealloc 
只做一件事:调用 Objective-C runtime 中object_dispose() 方法

>4. 调用 object_dispose()
为 C++ 的实例变量们(iVars)调用 destructors
为 ARC 状态下的 实例变量们(iVars) 调用 -release 
解除所有使用 runtime Associate方法关联的对象 
解除所有 __weak 引用 
调用 free()

6,_objc_msgForward函数是做什么的?直接调用它将会发生什么?

_objc_msgForward是IMP类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发,直接调用_objc_msgForward是非常危险的事,这是把双刃刀,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事。
JSPatch就是直接调用_objc_msgForward来实现其核心功能的

1.调用resolveInstanceMethod:方法,允许用户在此时为该Class动态添加实现。如果有实现了,则调用并返回。如果仍没实现,继续下面的动作。

2.调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接转发给它。如果返回了nil,继续下面的动作。

3.调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。

4.调用forwardInvocation:方法,将地3步获取到的方法签名包装成Invocation传入,如何处理就在这里面了。

7,objc_msgSend执行流程

oc的方法调用,其实就是转换为objc_msgSend的函数调用。简答的可以理解为发消息,如果 方法调用 之后出现了经典的错误,unrecognized selector sent to instance... 也可以从以下三个阶段进行分析。


图片.png

1、消息发送;

1 接收者首先从接收者类的cache中查找方法

1.1 如果能找到方法,直接调用,结束
1.2 如果找不到方法,继续执行2

2 从接收者类的方法列表中查找

2.1 如果找到方法,调用并将方法添加到接收者类的cache中,结束
2.2 如果找不到方法,则从其superClass的cache中查找
递归2.1,直到最顶层类。

3 如果找不到方法,则判断走以下两个步骤

3.1 如果两个步骤均不涉及,则直接抛出异常 'unrecognized selector sent to instance'
详细步骤参照以下阶段
    注:每个阶段结束会重新进入本阶段。

2、动态方法解析

针对未匹配的方法,我们可以通过 class_addMethod 给类添加新的方法和实现
重新进入消息发送阶段

3、消息转发或重新签名

如果在以上两个阶段均没有找到相关方法,此时就进入了消息转发阶段。消息转发主要有两个类别

直接转发
方法重签名,转发

消息直接转发

重写NSObject的 -(id)forwardingTargetForSelector:(SEL)aSelector方法
直接返回接收消息的对象实例。

方法重新签名

通过重写NSObject的方法
  • (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
  • (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector

    在方法中,我们针对reSignature selector进行了重新签名

重写NSObject方法

  • (void)forwardInvocation:(NSInvocation *)anInvocation

    方法中,对 reSignatureMethod selector的target进行了重新赋值

    唤醒

进入方法发送阶段

8,能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

  • 不能向编译后得到的类中增加实例变量;

分析:因为编译后的类已经注册在runtime中,类结构体中的objc_ivar_list 实例变量的链表和instance_size实例变量的内存大小已经确定,同时runtime 会调用class_setIvarLayout 或 class_setWeakIvarLayout来处理strong weak引用,所以不能向存在的类中添加实例变量。

  • 能向运行时创建的类中添加实例变量;

运行时创建的类是可以添加实例变量,调用 class_addIvar函数,但是得在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上。

9,简述下Objective-C中调用方法的过程(runtime)

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

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

10,什么是method swizzling(俗称黑魔法)

简单说就是进行方法交换

  • 在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。

  • 每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实就是方法名,IMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP。


    image
  • 交换方法的几种实现方式

    • 利用 method_exchangeImplementations 交换两个方法的实现
    • 利用 class_replaceMethod 替换方法的实现
    • 利用 method_setImplementation 来直接设置某个方法的IMP


      图片.png

11,对象如何找到对应的方法去调用

  • 方法保存到什么地方?
    对象方法保存到类中,类方法保存到元类(meta class),每一个类都有方法列表methodList。

  • 明确去哪个类中调用?

通过isa指针

1.根据对象的isa去对应的类查找方法,isa:判断去哪个类查找对应的方法 指向方法调用的类。
2.根据传入的方法编号SEL,里面有个哈希列表,在列表中找到对应方法Method(方法名)。
3.根据方法名(函数入口)找到函数实现,函数实现在方法区

你可能感兴趣的:(iOS Runtime经典面试题整理)