IOS底层探索(一)OC对象内存分析

IOS底层探索(一)OC对象内存分析

11.1 OC对象占用内存原理

  1. OC对象 最少占用 16 个字节内存.
  2. 当对象中包含属性, 会按属性占用内存开辟空间. 每一行 16 个字节中, 剩余内存如果可以放下剩余其中一个属性 (参考倒数第二张图) , 则会在行末存储 (注意: 并非一定是按照定义顺序来开辟空间, 放不下就开辟这样). 放不下时会重新开辟一行存储.
    最终满足 16 字节对齐标准.

11.1.1 初始OC对象占用内存

  • 创建一个 Command Line Tool 工程 , 打开 main.m 在 main 函数创建一个 NSObject.
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *objc = [[NSObject alloc] init];
    }
    return 0;
}
  • 打开终端/iTerm2 , 进入到 main.m 目录. 将其转换为 c++ 源码.
clang -rewrite-objc main.m -o main.cpp
  • 文件夹目录里多出一个 main.cpp 文件 , 打开. 看到7703行代码,不要慌.我们只需要关注 NSObject 即可. 搜索 NSObject_IMPL.
    IOS底层探索(一)OC对象内存分析
  • 这个就是 NSOject 对象对应的 C++ 结构体. 里面包含了一个 Class 指针. 搜索发现
typedef struct objc_class *Class;
  • 其实就是一个指向 struct objc_class 结构体类型的指针. 那么也就是说目前我们只发现 NSObject 对象对应的结构体只包含一个 isa 指针变量 , 一个指针变量在 64 位的机器上大小是 8 个字节.

  • 那是不是说一个 NSObject 对象就占用8个字节大小的内存呢?实际上不是的. 答案其实是: 所有的OC对象至少为16字节.

  • 我们先来验证一下. (有兴趣的可以去看看刚刚 main.cpp 中最下面 main 函数中 对象的创建源码)

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *lbobjc = [[NSObject alloc] init];
        
        NSLog(@"lbobjc对象实际需要的内存大小: %zd",class_getInstanceSize([lbobjc class]));
        NSLog(@"lbobjc对象实际分配的内存大小: %zd",malloc_size((__bridge const void *)(lbobjc)));
    }
    return 0;
}
  • 打印结果如下:

IOS底层探索(一)OC对象内存分析

  • 接下来,我们先来打个断点看看

IOS底层探索(一)OC对象内存分析

  • 接下来,我们通过断点来查看内存,这里先介绍一下查看内存的方法。

11.1.1.1 查看内存具体内容方法

  • 打开内存查看工具:

IOS底层探索(一)OC对象内存分析

  • 打开后会进入这个页面:
    IOS底层探索(一)OC对象内存分析

  • 地址栏中输入对象地址: NSObject的内存地址 0x102806240
    IOS底层探索(一)OC对象内存分析

  • lldb下输入调式命令 p lbobjc 查看对象指针地址,看到地址:0x0000000102806240,然后输入x 0x0000000102806240
    IOS底层探索(一)OC对象内存分析

  • 两种方法都表明, 目前我们创建的对象 后面几个字节全部为 00 .

  • 我们可以通过阅读 objc4 的源码来找到答案。通过查看跟踪 obj4allocallocWithZone 两个函数的实现,会发现这个连个函数都会调用一个 instanceSize 的函数:

size_t instanceSize(size_t extraBytes) {
     size_t size = alignedInstanceSize() + extraBytes;
      // CF requires all objects be at least 16bytes.
      if (size < 16) size = 16;
      return size; 
}
  • 上面源码中我们看出了答案, 最少会开辟16个字节. 那么为什么非要用 16 个字节来存储 8 个字节的内容呢?
  • 这里简单解释一下 .
  • 其实这里主要是涉及到硬件问题, 因为不同厂商之间需要一套标准化方案来解决不同厂商之间规则不同导致内存读取使用出现不统一的情况.为了解决这种问题而产生的 字节对齐.
  • 讲到这里,我还想继续看下 当这个对象包含多个属性时使用内存情况. 以便我们彻底搞明白 OC 对象使用内存情况.

11.1.1.2 查看属性占用内存情况

  • 创建一个 LBPerson 类,继承与 NSObject , 其包含三个 int 属性
@interface KylPerson : NSObject
@property (nonatomic,assign) int age;
@property (nonatomic,assign) int height;
@property (nonatomic,assign) int row;
@end
  • 回到main.m文件的main()函数,修改代码如下:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
#import "KylPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        //NSObject *kylobjc = [[NSObject alloc] init];
        KylPerson *kp = [[KylPerson alloc] init];
        kp.age = 26;
        kp.height = 180;
        kp.row = 10;
        
        NSLog(@"kylobjc对象实际需要的内存大小: %zd",class_getInstanceSize([kp class]));
        NSLog(@"kylobjc对象实际分配的内存大小: %zd",malloc_size((__bridge const void *)(kp)));
    }
    return 0;
}

  • 打印结果为:
    IOS底层探索(一)OC对象内存分析

  • 和上面步骤一样,打断点通过lldb命令查看内存
    IOS底层探索(一)OC对象内存分析

由于原本结构体 isa 指针占用8个, 然后 age 属性占用4个, height 占用 4个,此时 一组16字节刚好被占满 , row 属性再占用4个. 再次字节对齐,不足 16 补 16. 答案是 32 个字节.

  • 接下来我们对上面的属性类型做一点修改,我们将height属性由原来的int类型改为double类型:
@interface KylPerson : NSObject
@property (nonatomic,assign) int age;
@property (nonatomic,assign) double height;
@property (nonatomic,assign) int row;
@end
  • 重复上面的步骤,继续断点查看内存变化:

IOS底层探索(一)OC对象内存分析

  • 我们再来修改一下属性row的类型由原来的int改为double类型。
@interface KylPerson : NSObject
@property (nonatomic,assign) int age;
@property (nonatomic,assign) double height;
@property (nonatomic,assign) double row;
@end
  • 继续重复上面步骤,lldb断点查看内存变化如下:
    IOS底层探索(一)OC对象内存分析
上一篇:ruby-Serverspec无法正确检查软件包版本


下一篇:61 (OC)* 代理 block 通知 代理 kvo