美文网首页
十七、OC底层面试解析

十七、OC底层面试解析

作者: KB_MORE | 来源:发表于2020-10-27 11:05 被阅读0次

    【面试-1】方法的调用顺序

    类的方法 和 分类方法 重名,如果调用,是什么情况?

    • 如果同名方法是普通方法,包括initialize -- 先调用分类方法

      • 因为分类的方法是在类realize之后 attach进去的,插在类的方法的前面,所以优先调用分类的方法(注意:不是分类覆盖主类!!)

      • initialize方法什么时候调用? initialize方法也是主动调用,即第一次消息时调用,为了不影响整个load,可以将需要提前加载的数据写到initialize

    • 如果同名方法是load方法 -- 先 主类load,后分类load(分类之间,看编译的顺序)

    image

    【面试-2】Runtime是什么?

    • runtime是由C和C++汇编实现的一套API,为OC语言加入了 面向对象、以及运行时的功能

    • 运行时是指将数据类型的确定由编译时 推迟到了 运行时

      • 举例:extension 和 category 的区别
    • 平时编写的OC代码,在程序运行的过程中,其实最终会转换成runtime的C语言代码, runtime是OC的幕后工作者

    1、category 类别、分类

    • 专门用来给类添加新的方法

    • 不能给类添加成员属性,添加了成员属性,也无法取到

    • 注意:其实可以通过runtime 给分类添加属性,即属性关联,重写setter、getter方法

    • 分类中用@property 定义变量,只会生成变量的setter、getter方法的声明不能生成方法实现 和 带下划线的成员变量

    2、extension 类扩展

    • 可以说成是特殊的分类 ,也可称作 匿名分类

    • 可以给类添加成员属性,但是是私有变量

    • 可以给类添加方法,也是私有方法

    【面试-3】方法的本质,sel是什么?IMP是什么?两者之间的关系又是什么?

    • 方法的本质:发送消息,消息会有以下几个流程

      • 快速查找(objc_msgSend) - cache_t缓存消息中查找

      • 慢速查找 - 递归自己|父类 - lookUpImpOrForward

      • 查找不到消息:动态方法解析 - resolveInstanceMethod

      • 消息快速转发 - forwardingTargetForSelector

      • 消息慢速转发 - methodSignatureForSelector & forwardInvocation

    • sel方法编号 - 在read_images期间就编译进了内存

    • imp函数实现指针找imp就是找函数的过程

    • sel 相当于 一本书的目录title

    • imp 相当于 书本的页码

    • 查找具体的函数就是想看这本书具体篇章的内容

      • 1、首先知道想看什么,即目录 title - sel

      • 2、根据目录找到对应的页码 - imp

      • 3、通过页码去翻到具体的内容

    【面试-4】能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量

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

    • 2、只要类没有注册到内存还是可以添加的

    • 3、可以添加属性+方法

    【原因】:编译好的实例变量存储的位置是ro,一旦编译完成,内存结构就完全确定了

    【经典面试-5】 [self class]和[super class]的区别以及原理分析

    • [self class]就是发送消息 objc_msgSend,消息接收者是self,方法编号 class

    • [super class] 本质就是objc_msgSendSuper,消息的接收者还是 self,方法编号 class,在运行时,底层调用的是_objc_msgSendSuper2【重点!!!】

    • 只是 objc_msgSendSuper2 会更快,直接跳过self的查找

    代码调试

    • LGTeacher中的init方法中打印这两种class调用

      image

      运行程序,打印结果如下

      image
    • 进入[self class]中的class源码

    图片.png

    进入[self class]中的class源码

    - (Class)class {
        return object_getClass(self);
    }
    
    👇
    Class object_getClass(id obj)
    {
        if (obj) return obj->getIsa();
        else return Nil;
    }
    

    其底层是获取对象的isa,当前的对象是LGTeacher,其isa是同名的LGTeacher,所以[self class]打印的是LGTeacher

    [super class]中,其中super 是语法的 关键字,可以通过clangsuper的本质,这是编译时的底层源码,其中第一个参数是消息接收者,是一个__rw_objc_super结构

    image
    • 底层源码中搜索__rw_objc_super,是一个中间结构体

      image
    • objc中搜索objc_msgSendSuper,查看其隐藏参数

      image
    • 搜索struct objc_super

      image

      通过clang的底层编译代码可知,当前消息的接收者 等于 self,而self 等于 LGTeacher,所以 [super class]进入class方法源码后,其中的self 即为LGTeacher,所以最后还是获取LGTeacher的isa,即同名LGTeacher元类

    • 我们再来看[super class]在运行时是否如上一步的底层编码所示,是objc_msgSendSuper,打开汇编调试,调试结果如下

      image
      • 搜索objc_msgSendSuper2,从注释得知,是从 类开始查找,而不是父类

        image
      • 查看objc_msgSendSuper2的汇编源码,是从superclass中的cache中查找方法

    ENTRY _objc_msgSendSuper2
    UNWIND _objc_msgSendSuper2, NoFrame
    
    ldp p0, p16, [x0]       // p0 = real receiver, p16 = class 取出receiver 和 class
    ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
    CacheLookup NORMAL, _objc_msgSendSuper2//cache中查找--快速查找
    
    END_ENTRY _objc_msgSendSuper2
    

    完整回答

    所以,最完整的回答如下

    [self class]方法调用的本质是 发送消息,调用class的消息流程,拿到元类的类型,在这里是因为类已经加载到内存,所以在读取时是一个字符串类型,这个字符串类型是在map_imagesreadClass时已经加入表中,所以打印为LGTeacher
    [class class]打印的是LGTeacher,原因是当前的super是一个关键字,在这里只调用objc_msgSendSuper2,其实他的消息接收者和[self class]是一模一样的,所以返回的是LGTeacher

    相关文章

      网友评论

          本文标题:十七、OC底层面试解析

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