文章目录
前言
对第一周学习内容做个概括
提示:以下是本篇文章正文内容,下面案例可供参考
内存分区
内存分区
内存一共分为五大区域,栈区,堆区,全局区,常量区,代码区
栈区
由编译器自动分配释放,存放函数的参数值
堆区
允许程序在运行时动态的申请某个大小的内存空间,一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。
和数据结构里面的堆不一样
全局区
全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后由系统释放。
文字常量区
常量字符串就放在这,它是只读的。程序结束后由系统释放。
程序代码区
存放程序的编译后的二进制代码,CPU执行的机器指令,并且是只读的。
程序在不同的阶段会在内存中占据不同的内存区域。
运行之前
运行之前,分为代码区,数据区,未初始化数据区。
运行之后
运行之后分为代码区,未初始化数据区,数据区,栈区,堆区
编译,链接
编译的过程
- 预处理
这一步做了这几件事
宏替换:源代码中使用的宏定义会被替换为对应的#define内容
头文件引入:(#include,#import)
使用对应的.h文件内容替换这一行的内容,所以尽量减少头文件的#import,使用@class代替,把#import放到.m文件中
处理条件编译指令:(#if,#else,#endif)
- 词法分析
把原文件中的代码转化为特殊的标记流,源码被分为一个一个的单词,在行尾Loc中都标记出了源码所在的对应的原文件和具体行数,方便在报错的时候定位问题
- 语法分析
把此词法分析生成的标记流,解析成为一个抽象语法树,这里面的每一节点也都标记了其在源码中的位置
- 静态分析
把源码转化为抽象语法树后,编译器就可以对这个树进行处理。静态分析会对代码进行错误检测,如出现方法被调用但是未定义,定义但是未使用的变量等。
类型检查:clang做检查,检查程序是否发送正确的消息给正确的对象,是否在正确的值上调用了正常函数
其他分析:ObjCUnusedIVarsChecker.cpp检查是否有生成了但是从未使用的变量。ObjCSelfInitChecker.cpp是检查在 你的初始化方法中中调用 self 之前,是否已经调用[self initWith…]或[super init]了
- 生成中间代码和优化
LLVM有三种表示形式,他们本质上是等价的
text:文本格式
memory:内存格式
bitcode:二进制格式
- 汇编
汇编器将上一步生成的刻度的汇编代码转化为机器代码,最终产物是以.o结尾的目标文件。
链接
在编译的最后一步汇编结束后,就到了链接这一步
将上一步产生的目标文件和引用的静态库链接起来,最终生成可执行文件,链接解决了目标文件和库之间的链接
ARC,MRC
在编译期干了什么
ARC会把能够相互抵消的retain、release、autorelease操作约简
ARC会分析对象的生存期需求,并在编译时自动插入适当的内存管理方法调用的代码
编译器还会为你生成合适的dealloc方法
将内存管理交由编译器和运行期组件来做,可以使代码得到多种优化
对象的底层
OC对象的本质就是结构体
struct NSObject_IMPL结构体事实上是一个Class类型的isa指针
objc_class是继承objc_object的
每个类的底层都会有一个Class类的isa指针。
Class底层是struct objc_class *类型,NSObject底层是struct objc_object结构体,id底层是struct objc_object *类型
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
id底层实现是struct objc_object 类型,怪不得声明id类型变量时 后面不用再加""了,因为它定义的时候就定义为了一个Class的指针
SEL是struct objc_selector 类型
IMP是void ()(void )函数指针类型
struct objc_class : objc_object {
//...省略无关代码
// Class ISA; //ISA(从objc_object继承过来的)
Class superclass; //指向其父类
cache_t cache; //缓存
class_data_bits_t bits; //类的数据
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
//...省略无关代码
}
所有对象都是以objc_object为模板继承过来的。
类是一个结构体,里面存放了isa、superClass、cache、bits等
isa指针保存着指向类对象的内存地址,类对象全局只有一个,因此每个类创建出来的对象都会默认有一个isa属性,保存类对象的地址,也就是class,通过class就可以查询到这个对象的属性和方法,协议
源码主要是初始化isa,两种方式:
通过cls初始化:
非nonpointer,存储着Class、Meta-Class对象的内存地址信息。
通过bits初始化:
nonpointer,进行一系列的初始化操作。
消息传递,消息转发
消息转发
iOS的消息转发是指当一个对象收到一个无法响应的消息时,其会通过多个方法转发该消息,直到能够响应为止
当消息接收者无法响应某个方法时,Objective-C消息传递机制会按照以下顺序进行转发
- 动态方法解析
- 备援接收者
- 完整消息转发
如果没有找到消息,该怎么处理呢?就需要消息转发。
消息转发机制大致可分为三个步骤:
- 动态方法解析
- 备援接受者
- 完整消息转发
消息传递
在 iOS 中,消息传递是指通过向对象发送消息并让其执行对应的方法,从而实现组件间的通信和协作。
在 Objective-C 中,消息传递是通过向对象发送消息来实现的。当一个对象收到消息时,Objective-C 运行时会根据消息所带的函数名(即 Selector),查找该对象所属的类中是否有对应的方法实现。如果找到,则运行时会执行该方法实现;如果没找到,则会先尝试进行动态方法解析,再尝试进行消息转发
在 Objective-C 中,消息直到运行时才绑定到方法实现上。编译器会将消息表达式转化为一个消息函数的调用。
IMP指针
IMP本质就是一个函数指针,这个被指向的函数包含一个接收消息的对象id,调用方法的SEL,以及一些方法参数,并返回一个id。因此我们可以通过SEL获得它所对应的IMP,在取得了函数指针之后,也就意味着我们取得了需要执行方法的代码入口,这样我们就可以像普通的C语言函数调用一样使用这个函数指针
IMP与SEL的区别与联系
SEL:类方法的指针,相当于一种编号,区别与IMP
IMP:函数指针,保存了方法的地址