OC内存

作者: 陈盼同学 | 来源:发表于2021-01-24 15:45 被阅读0次

https://opensource.apple.com/tarballs/objc4/
\color{rgb(255,0,0)}{}

一个NSObject对象占用多少内存?

答案:一个NSObject对象底层实现是一个结构体,纯净的NSObject对象的这个结构体里面就一个指针成员,所以占用内存就是存储一个指针变量所占用的大小。
展开讲就是在64bit机器实际上分配了16个字节的存储空间给NSObject对象,但是初始化时只使用了8个字节去存储isa指针。真正有使用的空间是:存储一个指针变量所占用的大小(64bit,8个字节。32bit,4个字节)

由这道题引出了下面一系列论证

展开前情:一个指针在64bit的机器上占用8字节
64位编译器下,以下数据类型占用字节数
char :1个字节
short int : 2个字节
int: 4个字节
double: 8个字节

怎么验证是一个指针变量大小呢?

1.通过地址内存查看
NSObject *obj = [[NSObject alloc]init];
可以通过打印 p/x obj地址后,把地址拷贝放到Debug->Debug Workflow->View Memory里可以看到只有8字节在使用(打断点才能调出View Memory,或者使用快捷键)
MJ在课堂里讲的是obj->isa地址查看
为了通过内存查看也是直接p/x obj的地址也才查看obj的分配情况。

(lldb) p/x obj
(NSObject *) $0 = 0x0000600002519760

然后把0x0000600002519760放到View Memory可以看到占用8字节。

2.通过class_getInstanceSize查看
还可以通过"#import <malloc/malloc.h>"里的malloc_size查看具体给obj分了多少空间(malloc_size返回指针所指向对象字节数。)
然后通过"#import <objc/runtime.h>"里的class_getInstanceSize查看具体利用空间

//运行时的class_getInstanceSize查看具体利用空间
//malloc_size查看具体分配空间;malloc_size接收的是个C对象,需要桥接,头文件里malloc_size定义如右extern size_t malloc_size(const void *ptr);
NSLog(@"%zd,%zd",class_getInstanceSize([NSObject class]),malloc_size((__bridge void *)obj)); // 8 16

通过函数打印可以看到占用是一个指针变量大小,下面在通过源码来验证

3.通过转C++文件查看
https://opensource.apple.com/tarballs/objc4/可以下载oc部分源码,从而查看源码实现
在源码里搜索allocwithZone可以看到size分配情况,调用时写的
//CF requires all objects be at least 16 bytes (源码注释里写的)
if(size < 16) size = 16;

我们平时编写的Objective-C代码,底层实现其实都是C\C++代码

所以Objective-C的面向对象都是基于C\C++的数据结构实现的

思考:Objective-C的对象、类主要是基于C\C++的什么数据结构实现的?

结构体

将Objective-C代码转换为C\C++代码

//通过clang命令,可以将Objective-C代码转换为C\C++代码,比如下面指令可以将main.m里的OC代码转成C++
clang -rewrite-objc main.m -o main.cpp
//但是不建议用clang直接转,因为没有指定平台、架构等各种东西,比如下面指定,运行在真机环境的,arm64架构的C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp

open ./ 是打开当前文件夹,然后把cpp拖到项目里便于查看

例如我们在main.m写一句

NSObject *obj = [[NSObject alloc]init]; //实例对象

这句话在oc文件里是这样定义的 (直接点击NSObject就可以看到)

@interface NSObject {
    Class isa;
}
@end

可以理解为obj指针指向的一个NSObject *类型在OC里是上面这样定义。

将NSObject *obj = [[NSObject alloc]init];转成C++文件后 (将文件转成CPP后,看到NSObject类就是一个结构体)

struct NSObject_IMPL{
    Class isa;   //占用8字节
}

为什么说NSObject_IMPL就是obj指向的实现呢,有两点猜测原因,其一,跟刚才oc的定义大致相同,都是只有一个指针;其次,下面的一些代码也可以论证,比如自定义对象里有age什么的,都是在这个NSObject_IMPL结构体里。所以就猜想NSObject_IMPL结构体是obj的实现。可以很明显的看到,只是存了一个isa,也就是一个指针,所以obj结构体里isa就是一个指向类对象的指针

由上面这些推论,可以看到NSObject类的本质就是结构体,结构体里有一个isa指针,所以便验证是NSObject对象占用一个指针变量大小的内存的题目

NSObject *obj = [[NSObject alloc]init];这句话在内存中是怎么体现的呢?

首先通过[[NSObject alloc]init]会在内存中分配一块内存给NSObject对象。分配完空间后,会将空间的地址值赋值给obj指针。由于NSObject对象本质就是一个结构体,而且结构体此时“唯一”且第一个元素是isa,所以结构体的地址值也就是isa的地址值了,此时obj指针存储的值就是isa变量的地址值,也是结构体的地址值。

以下自定义继承NSObject的类又是什么样的呢

@interface Student : NSObject
{
@public
    int _age;
    int _num;
}
@end

在经过下面三步初始化赋值后
Student *stu = [[Student alloc] init];
stu->_age = 1;  //(c写法)
stu->_num = 2;

转成C++文件后展示为

struct Student_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
    int _num;
};
struct NSObject_IMPL {
    Class isa;
};

为了便于查看,合成如下

struct Student_IMPL {
    Class isa;  //这里为了方便,把右边的NSObject_IMPL考到了左边    Student_IMPL这个类的。源文件展示为struct NSObject_IMPL{
                    //Class ias;
                //}   //占用8字节
    int _age;          //占用4字节
    int _num;         //占用4字节
};

此时如何验证Student在内存中是长上面合成的样子呢?

首先在.m里定义一个结构体
struct Student_IMPL {
    Class isa;
    int _age;
    int _num;
};
然后
Student *stu = [[Student alloc]init];
stu->_age = 1;
stu->_num = 2;
之后通过桥接,将stu转成Student_IMPL结构体形式,通过结构体访问结构体里的
struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
NSLog(@"_age = %d,_num = %d",stuImpl->_age,stuImpl->_num);
打印结果为
_age = 1,_num = 2

//通过运行时的class_getInstanceSize可以很便捷的看到占用内存(真正占用的)情况
#import <objc/runtime.h>
NSLog(@"%zd  %zd",class_getInstanceSize([NSObject class]),class_getInstanceSize([Student class])); // 8 16
//malloc_size可以看到分配了多少内存给stu这个指针指向的内存
Student *stu = [[Student alloc]init];
NSLog(@"%zd",malloc_size((__bridge void *)stu));

如何查看Student在内存中的布局呢?

方法一,通过View Memory,输入stu的地址值,可以看到前16个字节的存储情况(涉及到大端小端模式,小端模式从高地址开始读(从右向左读))
方法二,通过lldb指令(打印框的调试器)

      (lldb) print stu  (将内存地址打印出来了)  //print可简写成p   print是普通打印,想打印对象,要用po
      (Student *) $0 = 0x0000600003f20030
      (lldb) po stu   (以对象的形式打印)
      <Student: 0x600003f20030>
      (lldb) memory read 0x600003f20030  (通过memory read也可以读取stu的地址值里内存的情况,可以看到前16个字节的存储情况)
      0x600003f20030: 60 55 08 0f 01 00 00 00 01 00 00 00 02 00 00 00  `U..............
      0x600003f20040: 80 84 87 02 00 60 00 00 09 00 73 74 00 00 00 00  .....`....st....
#常用LLDB指令
memory read/数量格式字节数  内存地址
x/数量格式字节数  内存地址
memory  write  内存地址  数值 (修改内存中的值)

格式:
x是16进制,f是浮点,d是10进制

字节大小:
b:byte 1字节
h:half word 2字节
w:word 4字节
g:giant word 8字节

eg:
(lldb) x 0x600003f20030  (利用x读取一个地址值)
0x600003f20030: 60 55 08 0f 01 00 00 00 01 00 00 00 02 00 00 00  `U..............
0x600003f20040: 80 84 87 02 00 60 00 00 09 00 73 74 00 00 00 00  .....`....st....
(lldb) x/4xw 0x600003f20030  (第一个x读取一个地址值,4代表读四次,第二个x代表用16进制输出,w代表4个字节4个字节的去读)
0x600003f20030: 0x0f085560 0x00000001 0x00000001 0x00000002
(lldb) x/4dw 0x600003f20030  (第一个x读取一个地址值,4代表读四次,d代表用10进制输出,w代表4个字节4个字节的去读)
0x600003f20030: 252204384
0x600003f20034: 1
0x600003f20038: 1
0x600003f2003c: 2

延伸->一个person类,一个student,student继承自person,person继承NSObject,那么在64bit下,内存占用情况如何

/* Person */
@interface Person : NSObject
{
@public
    int _age;
}

/* Student */
@interface Student : Person
{
    int _no;
}
在C++文件里,分配情况如下
      struct NSObject_IMPL {
          Class isa;
      };
      
      struct Person_IMPL {
          struct NSObject_IMPL NSObject_IVARS; // 8个字节
          int _age; // 4个字节
      }; // 内存对齐后:16个字节
      
      struct Student_IMPL {
          struct Person_IMPL Person_IVARS; // 16个字节
          int _no; // 4个字节 (4个字节可以补在Person_IMPL里空余没使用的4字节里,所以Student_IMPL总体还是16字节)
      }; // 内存对齐后:16个字节
所以Person、Student都占用16字节

如果上题中Person长下面这样,,打印内存变成了Person占用24字节,分配了32字节 (添加一个字段,内存虽然只用4字节,但分配8字节(以8的倍数增长,为什么以8的倍数呢,因为这个结构体里最大的成员isa的内存占用是8,所以以最大的成员占有的倍数增长),下次再添加一个字段,补在这空余的4字节里,以此轮循)

/* Person */
@interface Person : NSObject
{
@public
   int _age;
   int _num;
   int _height;
}
@end
Person *per = [[Person alloc]init];
NSLog(@"%zd,%zd",class_getInstanceSize([Person class]),malloc_size((__bridge void *)per)); //24 32

内存占用以最大的成员占有的倍数增长,但是内存分配以16的倍数增长,为什么呢?
内存分配的时候也有内存对齐,以16的倍数来对齐

以上所讨论的占用内存,都是创建的实例对象所占用的内存。里面isa以及存储变量的具体值。

相关文章

  • OC 资料总结

    OC内存划分OC数据类型

  • NSObject 底层本质

    一、OC 转 C/C++ 二、NSObject 对象内存布局 三、NSObject 内存大小 四、OC 对象内存布...

  • OC底层原理汇总

    OC底层原理(一).alloc实际调用流程分析OC底层原理(二).内存分配与内存对齐OC底层原理(三)、isa、对...

  • 内存管理

    目录一、内存分区 1、RAM和ROM 2、内存的五大分区二、内存管理 1、OC内存管理是指什么?OC内存管理的本质...

  • Lesson 0-1 Objective-C basic

    6.OC 手动内存管理 OC 内存管理原则: 只要使用 alloc, new, copy, mutableCopy...

  • OC的内存管理

    1、OC的内存管理 OC是通过引用计数进行内存管理的,其核心思想遵循“谁创建谁释放;谁引用谁管理”。 OC的内存管...

  • 内存对齐

    知识点概要 OC对象内存对齐结构体内存对齐 OC对象内存对齐 计算内存大小的三种方式 1.sizeof:系统提供的...

  • iOS 内存管理

    1、只有OC对象才需要进行内存管理 1、OC对象存在堆中 2、非OC对象存在栈中(内存会被系统自动收回) ...

  • 内存管理

    一.内存基本介绍 1、OC内存管理的基本概念 2、OC内存管理的范围​管理范围:管理任何继承自NSObject的对...

  • OC内存

    https://opensource.apple.com/tarballs/objc4/[https://open...

网友评论

      本文标题:OC内存

      本文链接:https://www.haomeiwen.com/subject/tgemzktx.html