iOS 底层探究—–OC底层面试

前言

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

底层面试题

load 方法的调用、C++构造函数、initialize 之间的对比

777E5EB4-A596-404B-A27D-7FBFDC166E59.png

load的调用

  • load方法是在应用程序加载过程中(也就是dyld的加载流程中)被调用,而且是发生在 main 函数之前的。其中load方法在dyld加载流程中被调用的流程是:doModInitFunctions -> libSystem_initializer -> _objc_init -> _dyld_objc_notify_register ->load_images ->… 。

  • 在底层_objc_init的过程中进行注册,回调load_images,加载两个load加载表,先是类的 load 表,再是分类的 load 表;

  • objc 底层中,在对类的load方法进行处理时,是进行了递归操作的,目的是为了保证父类优先被处理, 所以load方法的调用顺序:父类 –> 子类 –> 分类;然而在分类中,load方法的调用顺序根据编译顺序为准;

initialize的调用

  • objc 中,initialize的调用,是在第一次消息发送的时候 lookupimporforward,所以说调用顺序,是load方法优先于initialize方法;

  • 分类的⽅法是在类realize之后attach进去的插在前⾯,所以如果分类中有initialize这个方法,会优先调⽤分类的这个方法,值得注意的是,并不是分类覆盖主类哦;

  • 同样的,如果子类没有实现initialize方法,就会查找并调用父类的initialize方法,并且会调用两次(isa 的走位图),如果子类和父类都实现了initialize,那么会优先调用父类的,再调用子类的(递归);

C++ 构造函数的调用

  • 如果C++构造函数是在objc源码中,那么他的调用流程:doModInitFunctions -> libSystem_initializer -> _objc_init -> static_init -> getLibobjcInitializers。而 load 方法 是在 _dyld_objc_notify_register函数后调用的,所以此时,是先调用 C++ 构造函数,再调用 load 方法;

  • 如果写在main函数里面,或者自己的代码中,那么则是先调用+load方法,再调用C++函数,再调用main函数;

小结

所以,

  • 如果C++构造函数是在objc源码中,那么三者的调用顺序是:C++ 构造函数 –> load 方法 –> initialize 方法;
  • 如果只是普通的C++函数,那么调用顺序是:load 方法 –> C++ 函数 –> initialize 方法;

附:详细底层原理:load和initialize

Runtime 是什么?

  • 其实 runtime 是由 CC++、 汇编实现的⼀套 API,为 OC 语⾔加⼊了⾯向对象,运⾏时的功能而已,并不是底层哦;

  • 运⾏时(Runtime)是指将数据类型的确定由编译时推迟到了运⾏时,就好比:类扩展(extension)和分类(category)的区别;更加具备运行时,因为我们所有的类是在编译时,就加载完毕了,当加了Runtime这些 api的使用,就把类里面的方法推迟到运行时,才加载,如:rwe,不在根据编译得到的 machO 进行获取数据,而是可以进行动态的处理,使我们面向对象能够面向切面。

  • 平时编写的OC代码,在程序运行过程中,其实最终会转换成RuntimeC语言代码,RuntimeObject-C 的幕后工作者。

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

  • ⽅法的本质:发送消息,消息会有以下⼏个流程
    • 1:快速查找(objc_msgSend)~ cache_t 缓存消息;
    • 2:慢速查找 ~ 递归⾃⼰|⽗类 ~ lookUpImpOrForward
    • 3:查找不到消息:动态⽅法解析 ~ resolveInstanceMethod
    • 4:消息快速转发 ~ forwardingTargetForSelector
    • 5:消息慢速转发~ methodSignatureForSelector&forwardInvocation
  • selimp
    • sel 是⽅法编号 ~ 在 read_images 期间就编译进⼊了内存;
    • imp 就是我们函数实现指针,找 imp 就是找函数的过程;
    • sel 就相当于书本的⽬录 tittle
    • imp 就是书本的⻚码;
  • 查找具体的函数就是想看这本书⾥⾯具体篇章的内容:
    • 1:我们⾸先知道想看什么 ~ tittle(sel);
    • 2:根据⽬录对应的⻚码(imp);
    • 3:翻到具体的内容;

能否向编译后的得到的类中增加实例变量?能否想运⾏时创建的类中添加实例变量

  • 1、不能向编译后的得到的类中增加实例变量:
    • 我们编译好的实例变量存储的位置在ro,⼀旦编译完成,内存结构就完全确定;
    • 可以通过分类向类中添加方法和属性(关联对象);
  • 2、只要没有注册到内存还是可以添加的(没有执行objc_registerClassPair),见下面代码:

B781804D-1071-4D50-9006-5A2F18D71326.png

未执行了objc_registerClassPair,可以添加,执行了,就添加不了了。

[self class][super class] 的区别以及原理分析

创建两个类 LGPersonLGTeacher,其中 LGTeacher 继承于 LGPerson,然后在LGTeacher里面打印类,如下图:

F0B48D13-EBF5-47AF-9379-43B98BA84F90.png

然而,根据打印结果,是两个LGTeacher,为什么了?

我们可以通过终端指令, clang -rewrite-objc LGTeacher.m -o LGTeacher.cpp得到LGTeacher.cpp,再在里面找到 [self class][super class]C++代码,如下:

AC932F00-FEC6-46E6-95F0-D2CF4504B176.png

既然在C++里面,[super class]对应的是objc_msgSendSuper函数,那么我们去 objc 源码里面查找 objc_msgSendSuper,看一下它的结构:

objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )

    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
复制代码

objc_super 的结构是:

struct objc_super {
    __unsafe_unretained _Nonnull id receiver;
    __unsafe_unretained _Nonnull Class super_class;
};
复制代码

通过objc_super函数能找到 receiver(接收者)和 super_class(父类),作为 objc_msgSendSuper的参数,传入进去。

我们可以通过设置断点,看汇编,来跟踪 objc_super 的执行情况:

5a048394b1664497a58560491761d6bf~tplv-k3u1fbpfcp-watermark.image.png

根据汇编,知道真正调用的是objc_msgSendSuper2,那么我们看下他的结构:

objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
复制代码

可以发现,是和 objc_msgSendSuper 结构一样的。那么他们之间有什么联系了,我们直接去 objc 源码的汇编层去看:

B813750D-4BF5-45F0-9BAE-82FCAECDE13D.png

  • 可以看到当调用的是objc_msgSendSuper2函数,而传入的是当前类 (receiver --> self),通过汇编源码来获取父类,再调用CacheLookup函数;

  • 如果调用的是objc_msgSendSuper函数,直接传入的就是获取好的父类,然后跳到L_objc_msgSendSuper2_body也调到了CacheLookup函数。

所以[super class]是从父类开始查找方法,但是最终拍板的还是要看class的方法实现。

- (Class)class { 
    return object_getClass(self); 
} 
复制代码

Class object_getClass(id obj) { 
    if (obj) 
        return obj->getIsa(); 
    else 
        return Nil; 
}
复制代码

所以说,self是我们传入的隐藏参数也就是LGTeacher对象,那么它的isa自然就是LGTeacher类,从而 [super class] 打印出来的,还是 LGTeacher

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享