内存 iOS 内存分区 高地址 栈区 临时变量,参数等 系统管理,可读可写 FILO 先进后出 连续的内存地址 地址 0x7开头 堆区 alloc,malloc 申请分配地址 手动管理内存,可读可写 不连续的内存地址 链表结构 先进先出 FIFO 内存空间分配灵活,不过也会导致内存碎片 用户管理内存,可能导致内存泄露 内存分配:动态, 速度比栈等都慢 地址 常以0x6 开头 全局/静态区 未分配全局变量区 .bss 已分配全局变量区 .data 以 0x1 开头 退出app时销毁,可读可写 运行中对象内存一直在 常量区 编译时加载,退出app时销毁 代码中的常量申请内存 只读 代码区 二进制代码数据 只读 编译时加载,退出app时销毁 保留区 低地址 二进制重排 启动优化中我们介绍了二进制重排,通过修改文件加载顺序,从而改变函数加载 顺序,从而减少虚拟内存映射真实内存地址缺页问题,最终达到优化加载速度效 果,减少启动时间 详细参考优化篇内容,本节不再介绍 下面要介绍的是字节对齐,本质上也是内存读取速度优化的一种策略 内存(字节)对齐 iOS中创建一个对象, 并且这个对象还有属性,比如 int age 占 4字节, NSString *name 占 8 字节,等等,那么这个对象在编译分配 内存占用大小时, 会根据属性编写的先后顺序来计算 一个对象至少分配8字节,iOS 64位中至少是16字节,没有任何属性时,多出8 字节,这样保证至少以16字节作单位,方便计算指针位置,也方便扩展 如果属性本身 有嵌套对象,那么一小一大混合排序 过多的话,会产生内存“碎 片”,所以尽量定义属性时,把代码提前排好,这样能节省内存空间 iOS系统本身也是不断更新的,所以也会为 其进行优化 内存平移 简单理解就是内存地址上的 指针移动 一个实例对象调用对象方法,首先去对应的类对象(isa指向对应类对象 首地 址)缓存cache 和方法列表中寻找,通过指针移动来找对应的方法 函数地址,然 后进行调用 Person 类 : @interface LGPerson : NSObject - (void)sayHello; @end @implementation LGPerson - (void) saySomething { NSLog(@"%s: 你好", __func__); } @end 测试1: @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // 指针读取类地址,强转为对象,调用sayHello。 Class cls = [LGPerson class]; void * ht = &cls; [(__bridge id)ht saySomething]; // 实例化对象,调用sayHello LGPerson * person = [LGPerson new]; [person saySomething]; } @end 打印结果: 打印如下: -[LGPerson saySomething] : 你好 -[LGPerson saySomething] : 你好 通过类对象竟然成功调用 实例对象的 方法 !!! 测试1结果可知,通过类地址强转获得地址 和 实例对象 地址 应该是相同的,不 然怎么会调用成功呢 也就是说,测试1代码中两种实现方法都指向 Person 类对象的地址 isa 为什么首地址都是指向 isa ? 我们回顾下 class 的结构: struct objc_class : objc_object { // objc_object 内包含 isa Class superclass; // 父类,元类对象 cache_t cache; // 缓存表 class_data_bits_t bits; // class_rw_t内部用来获取基础信息的标记 class_rw_t *data() { // 可读可写结构:方法,属性,协议 ,对象信息 等 都整合到此处 return bits.data(); } ... } objc_object 结构里就是一个 isa ,继承过来的,其顺序在第一位。 也就是说,类对象起始地址就是指向 isa 的,或者说就是 isa 地址。 【isa 占8 字节】 另外,获取对象第一个属性时,肯定至少平移8字节,第二个属性的话就不一 定,可能中间隔断,具体还是以获取到对象属性的偏移位置为准 小结: 测试1 通过类直接强转后指向是类的首地址 isa 位置,刚好和实例对象 要去类对 象寻找方法指向isa 一致,这个巧合导致编译器未能判断前者不是真正的实例对 象,对于编译器来说,它只负责发送指令就行,管你是怎么来的呢 巧合往往也是不稳定的,测试1 中,person 我们没有设置过属性,假如我们设 置一个属性呢,会不会就有问题了,因为通过类直接强转的对象是不能设置属性 接下来我们设置一个属性,然后在方法里打印一下看看 测试2 修改结构: 给person 结构增加一个 NSString *kc_hobby;属性。 修改方法: - (void) saySomething { NSLog(@"%s - %@",__func__,self.kc_hobby); //kc_hobby是个字符串 } 接着再运行一遍,看结果: 第一个打印: ViewController 第二个打印:null 第二个实例对象因为还没设置属性值打印 null 正常,但是 第一个强转 对象打印 却打印到 控制器了 !!!! 【这里内存逻辑还没有搞明白,后面再补充】 经打印得知内存顺序为: self -> _cmd -> ViewController -> self -> cls -> ht(TODO) 平移8字节后指向了self,导致打印到控制器了,如果指向未知地址有可能崩溃, 所以会存在问题,用来测试可以。 主要是理解内存平移概念 内存泄露 这个在 优化篇 介绍 主要是指因循环引用导致对象内存得不到释放 内存溢出 直接加载大图等资源文件,或内存中被大量占用,内存不足导致问题 循环创建对象,短时间没有得到释放,最终导致内存分配失败