iOS-底层原理 20:OC底层面试解析

简介: iOS-底层原理 20:OC底层面试解析

【面试-1】Runtime Asssociate方法关联的对象,需要在dealloc中释放?


当我们对象释放时,会调用dealloc


  • 1、C++函数释放 :objc_cxxDestruct
  • 2、移除关联属性:_object_remove_assocations
  • 3、将弱引用自动设置nil:weak_clear_no_lock(&table.weak_table, (id)this);
  • 4、引用计数处理:table.refcnts.erase(this)
  • 5、销毁对象:free(obj)


所以,关联对象不需要我们手动移除,会在对象析构即dealloc时释放


dealloc 源码


dealloc的源码查找路径为:dealloc -> _objc_rootDealloc -> rootDealloc -> object_dispose(释放对象)-> objc_destructInstance -> _object_remove_assocations


  • 在objc源码中搜索dealloc的源码实现

image.png

进入_objc_rootDealloc源码实现,主要是对对象进行析构

image.png

进入rootDealloc源码实现,发现其中有关联属性时设置bool值,当有这些条件时,需要进入else流程

image.png

进入object_dispose源码实现,主要是销毁实例对象

image.png进入objc_destructInstance源码实现,在这里有移除关联属性的方法

image.png

进入_object_remove_assocations源码,关联属性的移除,主要是从全局哈希map中找到相关对象的迭代器,然后将迭代器中关联属性,从头到尾的移除


image.png


【面试-2】方法的调用顺序


类的方法 和 分类方法 重名,如果调用,是什么情况?


  • 如果同名方法是普通方法,包括initialize -- 先调用分类方法
  • 因为分类的方法是在类realize之后 attach进去的,插在类的方法的前面,所以优先调用分类的方法(注意:不是分类覆盖主类!!)
  • initialize方法什么时候调用? initialize方法也是主动调用,即第一次消息时调用,为了不影响整个load,可以将需要提前加载的数据写到initialize
  • 如果同名方法是load方法 -- 先 主类load,后分类load(分类之间,看编译的顺序)

image.png


【面试-3】Runtime是什么?


  • runtime是由C和C++汇编实现的一套API,为OC语言加入了 面向对象、以及运行时的功能
  • 运行时是指将数据类型的确定由编译时 推迟到了 运行时
  • 举例:extension 和 category 的区别
  • 平时编写的OC代码,在程序运行的过程中,其实最终会转换成runtime的C语言代码, runtime是OC的幕后工作者


1、category 类别、分类


  • 专门用来给类添加新的方法
  • 不能给类添加成员属性,添加了成员属性,也无法取到
  • 注意:其实可以通过runtime 给分类添加属性,即属性关联,重写setter、getter方法
  • 分类中用@property 定义变量,只会生成变量的setter、getter方法的声明不能生成方法实现 和 带下划线的成员变量


2、extension 类扩展


  • 可以说成是特殊的分类 ,也可称作 匿名分类
  • 可以给类添加成员属性,但是是私有变量
  • 可以给类添加方法,也是私有方法


【面试-4】方法的本质,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、通过页码去翻到具体的内容


【面试-5】能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量


  • 1、不能向编译后的得到的类中增加实例变量
  • 2、只要类没有注册到内存还是可以添加的
  • 3、可以添加属性+方法


【原因】:编译好的实例变量存储的位置是ro,一旦编译完成,内存结构就完全确定了


【经典面试-6】 [self class]和[super class]的区别以及原理分析


  • [self class]就是发送消息 objc_msgSend,消息接收者是self,方法编号 class
  • [super class] 本质就是objc_msgSendSuper,消息的接收者还是 self,方法编号 class,在运行时,底层调用的是_objc_msgSendSuper2【重点!!!】
  • 只是 objc_msgSendSuper2 会更快,直接跳过self的查找


代码调试


  • LGTeacher中的init方法中打印这两种class调用

image.png

运行程序,打印结果如下

image.png

  • 进入[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 是语法的 关键字,可以通过clangsuper的本质,这是编译时的底层源码,其中第一个参数是消息接收者,是一个__rw_objc_super结构

image.png

底层源码中搜索__rw_objc_super,是一个中间结构体

image.png

objc中搜索objc_msgSendSuper,查看其隐藏参数

image.png

搜索struct objc_super

image.png


  • 通过clang的底层编译代码可知,当前消息的接收者 等于 self,而self 等于 LGTeacher,所以 [super class]进入class方法源码后,其中的self是init后的实例对象,实例对象的isa指向的是本类,即消息接收者是LGTeacher本类


  • 我们再来看[super class]在运行时是否如上一步的底层编码所示,是objc_msgSendSuper,打开汇编调试,调试结果如下

image.png搜索objc_msgSendSuper2,从注释得知,是从 类开始查找,而不是父类


image.png

  • 查看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_imagesreadClass时已经加入表中,所以打印为LGTeacher
  • [super class]打印的是LGTeacher,原因是当前的super是一个关键字,在这里只调用objc_msgSendSuper2,其实他的消息接收者和[self class]是一模一样的,所以返回的是LGTeacher


【面试-7】内存平移问题


Class cls = [LGPerson class];
void  *kc = &cls;  //
[(__bridge id)kc saySomething];

LGPerson中有一个属性 kc_name 和一个实例方法saySomething,通过上面代码这种方式,能否调用实例方法?为什么?


代码调试


  • 我们在日常开发中的调用方式是下面这种


LGPerson *person = [LGPerson alloc];
[person saySomething];

通过运行发现,是可以执行的,打印结果如下

image.png

[person saySomething]的本质是对象发送消息,那么当前的person是什么?

  • personisa指向类LGPersonperson的首地址 指向 LGPerson的首地址,我们可以通过LGPerson的内存平移找到cache,在cache中查找方法


image.png

[(__bridge id)kc saySomething]中的kc是来自于LGPerson 这个类,然后有一个指针kc,将其指向LGPerson的首地址

image.png

所以,person是指向LGPerson类的结构,kc也是指向LGPerson类的结构,然后都是在LGPerson中的methodList中查找方法

image.png


修改:saySomething里面有属性 self.kc_name 的打印


代码如下所示

- (void)saySomething{
    NSLog(@"%s - %@",__func__,self.kc_name);
}
//下面这两种方式调用
//方式一
Class cls = [LGPerson class];
void  *kc = &cls; 
[(__bridge id)kc saySomething]; 
//方式二:常规调用
LGPerson *person = [LGPerson alloc];
 [person saySomething];

查看这两种调用方式的打印结果,如下所示


  • kc方式的调用打印的kc_name<ViewController: 0x7fe29170b560>
  • person方式的调用打印的kc_name(null)

image.png为什么会出现打印不一致的情况?


  • 其中person方式的kc_name 是由于 self指向person的内存结构,然后通过内存平移8字节,取出去kc_name,即self指针首地址平移8字节获得
    image.png


【方式一】其中kc指针中没有任何,所以kc表示8字节指针self.kc_name的获取,相当于 kc首地址的指针也需要平移8字节找kc_name,那么此时的kc的指针地址是多少?平移8字节获取的是什么?


  • kc是一个指针,是存在中的,栈是一个先进后出的结构,参数传入就是一个不断压栈的过程,


  • 其中隐藏参数会压入栈,且每个函数都会有两个隐藏参数(id self,sel _cmd),可以通过clang查看底层编译
  • 隐藏参数压栈的过程,其地址是递减的,而栈是从高地址->低地址 分配的,即在栈中,参数会从前往后一直压
  • super通过clang查看底层的编译,是objc_msgSendSuper,其第一个参数是一个结构体__rw_objc_super(self,class_getSuperclass),那么结构体中的属性是如何压栈的?可以通过自定义一个结构体,判断结构体内部成员的压栈情况


  • p &person3
  • p *(NSNumber **)0x00007ffee83a8090
  • p *(NSNumber **)0x00007ffee83a8098

image.png

  • 所以图中可以得出 20先加入,再加入10,因此结构体内部的压栈情况是 低地址->高地址递增的,栈中结构体内部的成员是反向压入栈,即低地址->高地址,是递增的,


  • 所以到目前为止,栈中从高地址到低地址的顺序的:self - _cmd - (id)class_getSuperclass(objc_getClass("ViewController")) - self - cls - kc - person
  • self_cmdviewDidLoad方法的两个隐藏参数,是高地址->低地址正向压栈
  • class_getSuperClassselfobjc_msgSendSuper2中的结构体成员,是从最后一个成员变量,即低地址->高地址反向压栈


可以通过下面这段代码打印下栈的存储是否如上面所说

void *sp  = (void *)&self;
void *end = (void *)&person;
long count = (sp - end) / 0x8;
for (long i = 0; i<count; i++) {
    void *address = sp - 0x8 * i;
    if ( i == 1) {
        NSLog(@"%p : %s",address, *(char **)address);
    }else{
        NSLog(@"%p : %@",address, *(void **)address);
    }
}

运行结果如下

image.png

其中为什么class_getSuperclassViewController,因为objc_msgSendSuper2返回的是当前类,两个self,并不是同一个self,而是栈的指针不同,但是指向同一片内存空间


  • [(__bridge id)kc saySomething]调用时,此时的kc是 LGPerson: 0x7ffeec381098,所以saySomething方法中传入的self 还是LGPerson,但并不是我们通常认为的LGPerson,使我们当前传入的消息接收者,即LGPerson: 0x7ffeec381098,是LGPerson的实例对象,此时的操作与普通的LGPerson是一致的,即LGPerson的地址内存平移8字节


  • 普通person流程:person -> kc_name - 内存平移8字节
  • kc流程:0x7ffeec381098 + 0x80 -> 0x7ffeec3810a0,即为self,指向<ViewController: 0x7fac45514f50>,如下图所示

image.png

其中 personLGPerson的关系是 person是以LGPerson为模板的实例化对象,即alloc有一个指针地址,指向isa,isa指向LGPerson,它们之间关联是有一个isa指向


而kc也是指向LGPerson的关系,编译器会认为 kc也是LGPerson的一个实例化对象,即kc相当于isa,即首地址,指向LGPerson,具有和person一样的效果,简单来说,我们已经完全将编译器骗过了,即kc也有kc_name。由于person查找kc_name是通过内存平移8字节,所以kc也是通过内存平移8字节去查找kc_name


哪些东西在栈里 哪些在堆里


  • alloc的对象 都在
  • 指针、对象中,例如person指向的空间中,person所在的空间在栈中
  • 临时变量
  • 属性值,属性随对象是在


注意:

  • 是从小到大,即低地址->高地址
  • 栈是从大到小,即从高地址->低地址分配
  • 函数隐藏参数会从前往后一直压,即 从高地址->低地址 开始入栈
  • 结构体内部的成员是从低地址->高地址
  • 一般情况下,内存地址有如下规则
  • 0x60 开头表示在
  • 0x70 开头的地址表示在
  • 0x10 开头的地址表示在全局区域


【面试-8】 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


底层源码调用流程如下图所示

image.png

相关文章
|
安全 算法 网络协议
解析:HTTPS通过SSL/TLS证书加密的原理与逻辑
HTTPS通过SSL/TLS证书加密,结合对称与非对称加密及数字证书验证实现安全通信。首先,服务器发送含公钥的数字证书,客户端验证其合法性后生成随机数并用公钥加密发送给服务器,双方据此生成相同的对称密钥。后续通信使用对称加密确保高效性和安全性。同时,数字证书验证服务器身份,防止中间人攻击;哈希算法和数字签名确保数据完整性,防止篡改。整个流程保障了身份认证、数据加密和完整性保护。
|
机器学习/深度学习 算法 数据挖掘
解析静态代理IP改善游戏体验的原理
静态代理IP通过提高网络稳定性和降低延迟,优化游戏体验。具体表现在加快游戏网络速度、实时玩家数据分析、优化游戏设计、简化更新流程、维护网络稳定性、提高连接可靠性、支持地区特性及提升访问速度等方面,确保更流畅、高效的游戏体验。
305 22
解析静态代理IP改善游戏体验的原理
|
机器学习/深度学习 数据可视化 PyTorch
深入解析图神经网络注意力机制:数学原理与可视化实现
本文深入解析了图神经网络(GNNs)中自注意力机制的内部运作原理,通过可视化和数学推导揭示其工作机制。文章采用“位置-转移图”概念框架,并使用NumPy实现代码示例,逐步拆解自注意力层的计算过程。文中详细展示了从节点特征矩阵、邻接矩阵到生成注意力权重的具体步骤,并通过四个类(GAL1至GAL4)模拟了整个计算流程。最终,结合实际PyTorch Geometric库中的代码,对比分析了核心逻辑,为理解GNN自注意力机制提供了清晰的学习路径。
780 7
深入解析图神经网络注意力机制:数学原理与可视化实现
|
编解码 缓存 Prometheus
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
本期内容为「ximagine」频道《显示器测试流程》的规范及标准,我们主要使用Calman、DisplayCAL、i1Profiler等软件及CA410、Spyder X、i1Pro 2等设备,是我们目前制作内容数据的重要来源,我们深知所做的仍是比较表面的活儿,和工程师、科研人员相比有着不小的差距,测试并不复杂,但是相当繁琐,收集整理测试无不花费大量时间精力,内容不完善或者有错误的地方,希望大佬指出我们好改进!
921 16
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
|
机器学习/深度学习 缓存 自然语言处理
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
Tiktokenizer 是一款现代分词工具,旨在高效、智能地将文本转换为机器可处理的离散单元(token)。它不仅超越了传统的空格分割和正则表达式匹配方法,还结合了上下文感知能力,适应复杂语言结构。Tiktokenizer 的核心特性包括自适应 token 分割、高效编码能力和出色的可扩展性,使其适用于从聊天机器人到大规模文本分析等多种应用场景。通过模块化设计,Tiktokenizer 确保了代码的可重用性和维护性,并在分词精度、处理效率和灵活性方面表现出色。此外,它支持多语言处理、表情符号识别和领域特定文本处理,能够应对各种复杂的文本输入需求。
1436 6
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
|
机器学习/深度学习 自然语言处理 搜索推荐
自注意力机制全解析:从原理到计算细节,一文尽览!
自注意力机制(Self-Attention)最早可追溯至20世纪70年代的神经网络研究,但直到2017年Google Brain团队提出Transformer架构后才广泛应用于深度学习。它通过计算序列内部元素间的相关性,捕捉复杂依赖关系,并支持并行化训练,显著提升了处理长文本和序列数据的能力。相比传统的RNN、LSTM和GRU,自注意力机制在自然语言处理(NLP)、计算机视觉、语音识别及推荐系统等领域展现出卓越性能。其核心步骤包括生成查询(Q)、键(K)和值(V)向量,计算缩放点积注意力得分,应用Softmax归一化,以及加权求和生成输出。自注意力机制提高了模型的表达能力,带来了更精准的服务。
13422 46
|
12月前
|
传感器 人工智能 监控
反向寻车系统怎么做?基本原理与系统组成解析
本文通过反向寻车系统的核心组成部分与技术分析,阐述反向寻车系统的工作原理,适用于适用于商场停车场、医院停车场及火车站停车场等。如需获取智慧停车场反向寻车技术方案前往文章最下方获取,如有项目合作及技术交流欢迎私信作者。
936 2
|
Java 数据库 开发者
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
1729 12
|
开发框架 监控 JavaScript
解锁鸿蒙装饰器:应用、原理与优势全解析
ArkTS提供了多维度的状态管理机制。在UI开发框架中,与UI相关联的数据可以在组件内使用,也可以在不同组件层级间传递,比如父子组件之间、爷孙组件之间,还可以在应用全局范围内传递或跨设备传递。
412 2
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

推荐镜像

更多
  • DNS