iOS 面试题分析

Posted 卡卡西Sensei

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS 面试题分析相关的知识,希望对你有一定的参考价值。

1.回顾

在之前的博客中,对OC底层进行了一系列的源码的探索分析,上一篇博客也对一些面试题进行了回答和分析,本篇博客继续面试题分析!

2. ios面试题分析

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

  • 方法的本质:发送消息流程
  1. 快速消息查找 (objc_msgSend),cache_t 缓存查找消息。
  2. 慢速消息查找(lookUpImpOrForward)递归自己以及父类,自己找不到去父类缓存中找,依然找不到会进行父类慢速查找,直到找到nil。
  3. 查找不到消息进行动态方法解析(resolveInstanceMethod/resolveClassMethod)。resolveClassMethod的过程中如果没有找到方法,会调用resolveInstanceMethod
  4. 消息快速转发(forwardingTargetForSelector),相当于找消息备用接收者。
  5. 消息慢速转发(methodSignatureForSelector & forwardInvocation),在仍然没有解决问题后在methodSignatureForSelector的时候会再进行一次慢速消息查找(这次不进行消息转发)。
  6. 最后仍然没有解决问题会进入doesNotRecognizeSelector抛出异常。
  • sel是方法编号,在read_images期间就编译进入了内存。

  • imp 就是函数实现指针 ,找imp就是找函数的过程。
    可以将sel-imp理解为书本的目录,sel书本目录的名称,imp就是书本的⻚码。查找具体的函数就是想看这本书里面具体篇章的内容。

  • 1:我们⾸先知道想看什么 – > tittle (sel)

  • 2:根据⽬录对应的⻚码 – >(imp)

  • 3:翻到具体的内容

  • impSEL 的关系
    SEL : ⽅法编号
    IMP : 函数指针地址
    SEL 相当于书本⽬录的名称
    IMP : 相当于书本⽬录的⻚码

  1. ⾸先明⽩我们要找到书本的什么内容 (sel⽬录⾥⾯的名称)
  2. 通过名称找到对应的本⻚码 (imp)
  3. 通过⻚码去定位具体的内容

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

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

原因:我们编译好的实例变量存储的位置在ro,⼀旦编译完成,内存结构就完全确定了,是⽆法进行任何修改的。

只要内没有注册到内存还是可以添加

可以通过关联对象的方式添加属性,方法等
主要用到了objc_setAssociatedObjectobjc_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

[self class]打印结果可以理解,这[super class]打印就很懵了

太意想不到了,那么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;
}
  • classNSObject的方法,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_superSEL

  • 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_msgSendSuperobjc_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_supersuperClass为父类,objc_msgSendSuper2传递的objc_supersuperClass为自己,在汇编代码中进行了父类的获取。

那么[super class]receiver决定了消息的接收者,从上面的解释中也可以知道,这里的接受者还是self也就是LGTeacher

所以[super class]也打印的是LGTeacher

  • llvm中实现源码如下:


更多内容持续更新

🌹 喜欢就点个赞吧👍🌹

🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹

🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹

以上是关于iOS 面试题分析的主要内容,如果未能解决你的问题,请参考以下文章

前端面试题之手写promise

猿创征文|iOS经典面试题之深入分析图像的解码渲染与基本原理

iOS 面试题分析

2021最全iOS面试题及底层视频分享专栏

从一道面试题分析Linux进程+IO缓冲区机制

iOS经典面试题之深入分析block相关高频面试题