美文网首页
iOS系列之Runtime

iOS系列之Runtime

作者: iOS的火影乱斗 | 来源:发表于2021-05-14 15:18 被阅读0次

    作者:洋仔
    链接:https://juejin.cn/post/6961653605652758564

    1.objc在向一个对象发送消息时,发生了什么?

    objc在向一个对象发送消息时,runtime会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果一直到根类还没找到,转向拦截调用,走消息转发机制,一旦找到 ,就去执行它的实现IMP 。

    2.objc中向一个nil对象发送消息将会发生什么?

    如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。也不会崩溃。

    详解: 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil);
    如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*) ,float,double,long double 或者long long的整型标量,发送给nil的消息将返回0;
    如果方法返回值为结构体,发送给nil的消息将返回0。结构体中各个字段的值将都是0;
    如果方法的返回值不是上述提到的几种情况,那么发送给nil的消息的返回值将是未定义的。

    3.objc中向一个对象发送消息[obj foo]和objc_msgSend()函数之间有什么关系?

    在objc编译时,[obj foo] 会被转意为:objc_msgSend(obj, @selector(foo));。

    4.什么时候会报unrecognized selector的异常?

    objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,会进入消息转发阶段,如果消息三次转发流程仍未实现,则程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。

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

    不能向编译后得到的类中增加实例变量;
    能向运行时创建的类中添加实例变量;
    1.因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表和 instance_size 实例变量的内存大小已经确定,同时runtime会调用 class_setvarlayout 或 class_setWeaklvarLayout 来处理strong weak 引用.所以不能向存在的类中添加实例变量。
    2.运行时创建的类是可以添加实例变量,调用class_addIvar函数. 但是的在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上.

    6.给类添加一个属性后,在类结构体里哪些元素会发生变化?

    instance_size :实例的内存大小;objc_ivar_list *ivars:属性列表
    

    7.一个objc对象的isa的指针指向什么?有什么作用?

    指向他的类对象,从而可以找到对象上的方法

    8.[self class] 与 [super class]

    @implementation Son : Father   - (id)init   {       self = [super init];       if (self) {           NSLog(@"%@", NSStringFromClass([self class]));           NSLog(@"%@", NSStringFromClass([super class]));       }       return self;   }   @end
    
    NSStringFromClass([self class]) = Son NSStringFromClass([super class]) = Son
    

    详解:这个题目主要是考察关于 Objective-C 中对 self 和 super 的理解。
    self 是类的隐藏参数,指向当前调用方法的这个类的实例;
    super 本质是一个编译器标示符,和 self 是指向的同一个消息接受者。不同点在于:super 会告诉编译器,当调用方法时,去调用父类的方法,而不是本类中的方法。
    当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;而当使用 super 时,则从父类的方法列表中开始找。然后调用父类的这个方法。
    在调用[super class]的时候,runtime会去调用objc_msgSendSuper方法,而不是objc_msgSend;
    由于找到了父类NSObject里面的class方法的IMP,又因为传入的入参objc_super->receiver = self。self就是son,调用class,所以父类的方法class执行IMP之后,输出还是son,最后输出两个都一样,都是输出son。

    题库资料已上传到Github:https://github.com/Henry-ley/rest/blob/main/README.md内附整理的学习思维导图以及一些iOS资料。

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

    每一个类对象中都一个方法列表,方法列表中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.

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

    _objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。

    11.runtime 如何实现 weak 属性??知道SideTable吗?

    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 表中删除,最后清理对象的记录。

    SideTable结构体是负责管理类的引用计数表和weak表,

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

    无论在MRC下还是ARC下均不需要,被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被 NSObject -dealloc 调用的object_dispose()方法中释放。

    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()
    

    13. 什么是method swizzling(俗称黑魔法)

    在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。
    每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实就是方法名,IMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP。
    换方法的几种实现方式

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

    14.runtime具体应用

    利用关联对象(AssociatedObject)给分类添加属性
    遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
    交换方法实现(交换系统的方法)
    利用消息转发机制解决方法找不到的异常问题
    KVC 字典转模型

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

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

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

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

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

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

    17.load和initialize的区别

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

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

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

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

    题库资料已上传到Github:https://github.com/Henry-ley/rest/blob/main/README.md内附整理的学习思维导图以及一些iOS资料。

    文章到这里就结束了,你也可以私信我及时获取最新资料以及面试相关资料。如果你有什么意见和建议欢迎给我留言。

    相关文章

      网友评论

          本文标题:iOS系列之Runtime

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