十七、OC底层面试解析
【面试-1】方法的调用顺序
类的方法 和 分类方法 重名,如果调用,是什么情况?
-
如果同名方法是
普通方法
,包括initialize
-- 先调用分类方法-
因为
分类的方法是在类realize之后 attach进去的
,插在类的方法的前面,所以优先调用分类的方法
(注意:不是分类覆盖主类!!) -
initialize
方法什么时候调用?initialize
方法也是主动调用,即第一次消息时
调用,为了不影响整个load,可以将需要提前加载的数据
写到initialize
中
-
-
如果同名方法是
load
方法 -- 先主类load
,后分类load
(分类之间,看编译的顺序)- 原因:参考十二、+ load方法分析
- 原因:参考十二、+ load方法分析
【面试-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的查找
代码调试
-
imageLGTeacher
中的init
方法中打印这两种class调用运行程序,打印结果如下
image -
进入
[self class]
中的class
源码
进入[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
是语法的 关键字
,可以通过clang
看super
的本质,这是编译时
的底层源码,其中第一个参数是消息接收者,是一个__rw_objc_super
结构
-
底层源码中搜索
image__rw_objc_super
,是一个中间结构体 -
objc中搜索
imageobjc_msgSendSuper
,查看其隐藏参数 -
搜索
imagestruct objc_super
通过
clang
的底层编译代码可知,当前消息的接收者
等于self
,而self
等于LGTeacher
,所以[super class]
进入class
方法源码后,其中的self
即为LGTeacher
,所以最后还是获取LGTeacher的isa
,即同名LGTeacher元类
-
我们再来看[super class]在运行时是否如上一步的底层编码所示,是
imageobjc_msgSendSuper
,打开汇编调试,调试结果如下-
搜索
imageobjc_msgSendSuper2
,从注释得知,是从 类开始查找
,而不是父类 -
查看
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_images
的readClass
时已经加入表中,所以打印为LGTeacher
[class class]打印的是LGTeacher,原因是当前的super是一个关键字,在这里只调用objc_msgSendSuper2
,其实他的消息接收者和[self class]是一模一样的,所以返回的是LGTeacher