OC底层面试解析(持续更新中...)

999 阅读6分钟
  • 关联对象是否需要移除

    但是不需要的,底层在dealloc方法中释放了关联对象
    源码探索:
    首先全局搜索dealloc找到dealloc方法的实现如下图:
    发现dealloc方法中是调用了_objc_rootDealloc方法,继续查看_objc_rootDealloc方法的源码实现继续查看rootDealloc方法的源码如果存在关联对象则会走到else中调用object_dispose方法,再看object_dispose方法的源码继续查看objc_destructInstance方法的源码该方法中又调用了_object_remove_assocations方法来移除关联对象通过源码验证了关联对象会在dealloc移除

  • 方法调用顺序

    一个类中的方法主要有load方法initialize方法,本类的方法,分类的方法,所以这个问题的大致意思就是要区分这四个方法的调用顺序,

    1. load方法的执行时机
      juejin.cn/post/692338…文章中知道load方法是在load_image方法中实现,又在juejin.cn/post/691944…文章中得知load_image方法会在dyld加载的时候被调用,所以基本上可以确定load方法是第一个执行的,但是load方法又分为本类的load方法和分类的load方法,看load_image方法的源码实现 所以可以确定分类的load方法会在本类load方法执行完成之后执行,本类中的load方法又可以分类父类load方法和子类的load方法,首先在call_class_loads知道load方法就是按照loadable_classes列表的顺序调用的,load方法的插入是在prepare_load_methods方法中处理的通过源码发现schedule_class_load方法中回归查找父类的方法回显将父类的所有load方法添加完成之后再添加子类的load方法所以可以确定的是子类的load方法会在本类load方法之后执行
      • 总结:load方法执行顺序:本类(父类)->本类(子类)->分类
      • 验证
    2. initialize方法的执行时机
      initialize是在类或者它的子类接受第一条消息前被调用,从文章juejin.cn/post/688866…中知道方法的本质其实就是消息的发送通过调用runtime objc_msgSend方法实现消息的发送,所以initialize方法是在所普通方法调用前被调用
      注意:load方法是直接通过地址调用的并没有消息转发的过程,所以load方法在initialize方法前执行
    3. 主类、分类同名方法的执行顺序
      从文章juejin.cn/post/692459…中知道分类的方法是插在类的方法的前面,所以优先调用分类的方法
    4. 结论:
      本类load方法(父类)-> 本类load方法 -> 分类load方法 -> initialize方法(分类中有实现的话则执行分类中的方法) -> 其他自定义方法(如果分类中有实现同名方法则执行分类的方法)
      结果验证:
  • Runtime是什么

    runtime 是由C 和C++ 汇编 实现的⼀套API,为OC语⾔加⼊了⾯向对象,运⾏时的功能

    运⾏时(Runtime)是指将数据类型的确定由编译时推迟到了运⾏时
    举例⼦:extension - category 的区别(可在文章juejin.cn/post/693090…中找答案)

    平时编写的OC代码,在程序运⾏过程中,其实最终会转换成Runtime的C语⾔代 码,RuntimeObject-C 的幕后⼯作者

  • ⽅法的本质,sel是什么?imp是什么?两者之间的关系⼜是什么

    在文章juejin.cn/post/688866…juejin.cn/post/689314…juejin.cn/post/689575…文章中都有详细的解释,方法的本质其实就奥豪斯消息发送,方法的调用编译之后都是调用runtimeobjc_masSend发送消息的,消息会有如下几个流程:

    1. 快速查找(objc_msgSend)~ cache_t缓存消息
    2. 慢速查找~递归⾃⼰|⽗类 ~ lookUpImpOrForward
    3. 查找不到消息: 动态⽅法解析 ~ resolveInstanceMethod
    4. 消息快速转发~ forwardingTargetForSelector
    5. 消息慢速转发~ methodSignatureForSelector & forwardInvocation

    sel 是⽅法编号 ~ 在read_images 期间就编译进⼊了内存
    imp 就是我们函数实现指针 ,找imp就是找函数的过程
    sel 就相当于书本的⽬录 tittle
    imp 就是书本的⻚码
    查找具体的函数就是想看这本书⾥⾯具体篇章的内容

    1. 我们⾸先知道想看什么 ~ tittle (sel)
    2. 根据⽬录对应的⻚码imp)
    3. 翻到具体的内容
  • 能否向编译后得到的类中增加实例变量?能否往运⾏时创建的类中添加实例变量?

    1. 不能像编译后得到的类中添加实例变量,应为实例变量是存储在ro中的ivar_list_t中的,编译完成,内存结构就完全确定,ro是read only只读的,加载到内存后不会发生改变又称为clean memory(干净内存)所以不能添加(属性、方法是存在rw中的rw是可在运行时动态修改的所以属性、方法是可以添加的)
    2. 可以往运⾏时创建的类中添加实例变量,类还没创建ro结构就没有确定,所以可以添加,可调用class_addIvar函数添加
  • [self class][super class]的区别以及原理分析

    1. 简单的通过打印判断发现其实没有区别

    2. clang一下看编译后的区别
      发现[self class]方法底层是调用objc_msgSend方法进行消息发送,消息接收者是self,方法编号是class[super class]是调用objc_msgSendSuper方法进行消息发送,消息接收者是self,方法编号是class

      点进class方法源码查看发现就是返回当前对象的isa,而isa指向当前类,所以步骤一[self class]打印当前类名

      在通过源码发现objc_msgSendobjc_msgSendSuper入参树不同的一个是id,还一个是objc_super可以看出消息的接收者依然是当前类,同时也可以通过注释看出objc_msgSendSuper是直接冲父类开始查找方法的,所以这里又一个区别点是superClass的传递和方法查找的顺序,应为消息的接收者依然是当前类所以验证了步骤一的打印结果依然是当前类名
      总结区别:

      1. [self class]调用objc_msgSend进行消息转发,[super class]调用objc_msgSendSuper进行消息转发
      2. [self class]不传递父类,[super class]传递父类
      3. [self class]从当前类开始方法查找,[super class]从父类开始方法查找
  • Runtime是如何实现weak的,为什么可以自动置nil

    1. 通过SideTable找到我们的weak_table
    2. weak_table根据referent找到或者创建weak_entry_t
    3. 然后append_referrer(entry,referrer)将我的新弱引用的对象加进去entry
    4. 最后 weak_entry_insert,把entry加入到我们的weak_table
  • Method-Swizzling 方法交换

    详情可看文章juejin.cn/post/693463…

  • KVC原理问题

    详情请看文章KVC原理探索

  • KVO原理问题

    详情请看文章KVO原理探索