iOS 面试题分析(二)
1.回顾
在之前的博客中,对OC底层进行了一系列的源码的探索分析,上一篇博客也对一些面试题进行了回答和分析,本篇博客继续面试题分析!
2. iOS面试题分析
2.1 ⽅法的本质?sel是什么?IMP是什么?两者之间的关系⼜是什么?
- 方法的本质:发送消息流程
- 快速消息查找 (
objc_msgSend
),cache_t
缓存查找消息。 - 慢速消息查找(
lookUpImpOrForward
)递归自己以及父类,自己找不到去父类缓存中找,依然找不到会进行父类慢速查找,直到找到nil。 - 查找不到消息进行动态方法解析(
resolveInstanceMethod
/resolveClassMethod
)。resolveClassMethod
的过程中如果没有找到方法,会调用resolveInstanceMethod
。 - 消息快速转发(
forwardingTargetForSelector
),相当于找消息备用接收者。 - 消息慢速转发(
methodSignatureForSelector
&forwardInvocation
),在仍然没有解决问题后在methodSignatureForSelector
的时候会再进行一次慢速消息查找(这次不进行消息转发)。 - 最后仍然没有解决问题会进入
doesNotRecognizeSelector
抛出异常。
-
sel
是方法编号,在read_images
期间就编译进入了内存。 -
imp
就是函数实现指针 ,找imp
就是找函数的过程。
可以将sel-imp
理解为书本的目录,sel
书本目录的名称,imp
就是书本的⻚码。查找具体的函数就是想看这本书里面具体篇章的内容。 -
1:我们⾸先知道想看什么 -- >
tittle (sel)
-
2:根据⽬录对应的⻚码 -- >
(imp)
-
3:翻到具体的内容
-
imp
与SEL
的关系
SEL
: ⽅法编号
IMP
: 函数指针地址
SEL
相当于书本⽬录的名称
IMP
: 相当于书本⽬录的⻚码
- ⾸先明⽩我们要找到书本的什么内容 (
sel
⽬录⾥⾯的名称) - 通过名称找到对应的本⻚码 (
imp
) - 通过⻚码去定位具体的内容
2.2 能否向编译后的得到的类中增加实例变量?能否向运⾏时创建的类中添加实例变量
不能向编译后的得到的类中增加实例变量
原因
:我们编译好的实例变量存储的位置在ro
,⼀旦编译完成,内存结构就完全确定了,是⽆法进行任何修改的。只要内没有注册到内存还是可以添加
可以通过关联对象的方式添加属性,方法等
主要用到了objc_setAssociatedObject
,objc_getAssociatedObject
以及objc_removeAssociatedObjects
方法
当我们的对象释放的时候 --> dealloc
1: c++
函数释放: object_cxxDestruct
2: 移除关联属性 : _object_remove_assocations
3: 将弱引⽤⾃动设置 nil
: weak_clear_no_lock(&table.weak_table, (id)this)
4: 引⽤计数处理: table.refcnts.erase(this)
4: 销毁对象: free(obj)
2.3 [self class]和[super class]的区别以及原理分析。
我先来看看如下,代码
@implementation LGTeacher
- (instancetype)init{
self = [super init];
if (self) {
NSLog(@"%@ - %@",[self class],[super class]);
}
return self;
}
代码运行结果如下:
2021-07-30 10:49:10.213169+0800 ObjcBuild[65615:2453340] LGTeacher--LGTeacher
what?
[self class]
打印结果可以理解,这[super class]
打印就很懵了
clang太意想不到了,那么
clang
一下看看,是objc_msgSendSuper
在这里插入图片描述之后
debug
一下,汇编跟踪看看
不看不要紧,一看更懵逼!什么鬼👻???不是发送
objc_msgSendSuper
消息吗?怎么又变成objc_msgSendSuper2
了啊!现在脑壳嗡嗡的!百思不得其解。
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
-
class
是NSObject
的方法,class
的隐藏参数是id self
,SEL _cmd
。所以[self class]
就是发送消息objc_msgSend
,消息接受者是self
和方法编号class
。所以返回LGTeacher
。 - 对于
super
来说它是没有这个参数的。它不是参数名,是一个编译器关键字
。在clang
中编译后调用的是objc_msgSendSuper
方法。
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
objc_msgSendSuper
有两个参数objc_super
和SEL
objc_super
/// Specifies the superclass of an instance.
struct objc_super {
__unsafe_unretained _Nonnull id receiver;
#if !defined(__cplusplus) && !__OBJC2__
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
};
#endif
其中的一个参数receiver
是消息接收者,super_class
为第一个被查找的类,但是实际它调用的是objc_msgSendSuper2
,上面也验证过了。
objc_msgSendSuper2
// objc_msgSendSuper2() takes the current search class, not its superclass.
OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
从源码的注释中也可以发现super_class
应该就是当前类。
-
objc_msgSendSuper
与objc_msgSendSuper2
实现如下:
ENTRY _objc_msgSendSuper
UNWIND _objc_msgSendSuper, NoFrame
//p0存储receiver,p16存储class
ldp p0, p16, [x0] // p0 = real receiver, p16 = class
//跳转到 L_objc_msgSendSuper2_body 的实现
b L_objc_msgSendSuper2_body
END_ENTRY _objc_msgSendSuper
// no _objc_msgLookupSuper
ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
#if __has_feature(ptrauth_calls)
ldp x0, x17, [x0] // x0 = real receiver, x17 = class
//读取
add x17, x17, #SUPERCLASS // x17 = &class->superclass
ldr x16, [x17] // x16 = class->superclass
AuthISASuper x16, x17, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS
LMsgSendSuperResume:
#else
//ldp读取两个寄存器,将objc_super解析成receiver和class
ldp p0, p16, [x0] // p0 = real receiver, p16 = class
//通过class找到superclass
ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
#endif
L_objc_msgSendSuper2_body:
//查找缓存
CacheLookup NORMAL, _objc_msgSendSuper2, __objc_msgSend_uncached
END_ENTRY _objc_msgSendSuper2
从上面arm64
的汇编源码可以知道_objc_msgSendSuper
跳转到_objc_msgSendSuper2
,区别是_objc_msgSendSuper
直接调用,objc_msgSendSuper2
通过cls
获取了superClass
。也就是说objc_msgSendSuper
传递的objc_super
中superClass
为父类,objc_msgSendSuper2
传递的objc_super
中superClass
为自己,在汇编代码中进行了父类的获取。
那么
[super class]
中receiver
决定了消息的接收者,从上面的解释中也可以知道,这里的接受者还是self
也就是LGTeacher
所以
[super class]
也打印的是LGTeacher
。
- 在
llvm
中实现源码如下:
更多内容持续更新
🌹 喜欢就点个赞吧👍🌹
🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹
🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹