前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OC 对象的本质

OC 对象的本质

作者头像
用户1890628
发布2018-05-10 14:52:48
1.1K0
发布2018-05-10 14:52:48
举报
文章被收录于专栏:Objective-CObjective-C

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

一个指针变量所占用的大小(64bit->8个字节,32bit->4个字节)

我们平时编写的Objective-C代码,底层实现都是C/C++代码,Objective-C的面向对象都是基于C/C++的数据结构实现的。

Objective-C -> C/C++ -> 汇编语言 -> 机器语言

如果想研究一些本质问题,最好将Objective-C代码转化成C/C++代码,才比较容易分析出来原理。

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

假设一个Person类,有下面属性

代码语言:javascript
复制
@interface Person : NSObject
{
    int _age;
    int _no;
    double _height;
    NSString *_name;
}
@end

对应C/C++是以结构体的形式存在的

代码语言:javascript
复制
struct Person {
    int _age;
    int _no;
    double _height;
    char *_name;
}
main.m中下面代码转成C++代码
代码语言:javascript
复制
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *obj = [[NSObject alloc] init];
        
        NSLog(@"%p", obj);
    }
    return 0;
}

终端输入如下命令

代码语言:javascript
复制
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

只生成在真机上arm64架构下的文件,如果不指定架构,则生成文件包含其它架构,并且文件要比这个大一点。

main.cpp中的cpp就是c plus plus的意思

并且项目中多了一个main-arm64.cpp文件

main-arm64.cpp文件用Xcode打开后,你会发现,虽然在main.m中只写了一行代码,转换成C++的代码就有3w多行。

main-arm64.cpp文件中可以发现在NSObjectIMPL(Imeplemetation)方法中只有一个Class isa

代码语言:javascript
复制
struct NSObject_IMPL {
    Class isa;
};

我们可以猜想在NSObject的底层实现,就是一个NSObject_IMPL

一个 OC 对象在内存中是如何布局的?

Foundation框架中我们可以看到NSObject类的声明

代码语言:javascript
复制
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
@end

可以省略成如下格式

代码语言:javascript
复制
@interface NSObject {
    Class isa;
}
@end

对比main-arm64.cppNSObject类的实现

代码语言:javascript
复制
struct NSObject_IMPL {
    Class isa;
};

相当于NSObject对象创建后在内存中就是以结构体的形式存在的。

该结构体中只有一个成员isa,为指针类型,在64位结构下占用8个字节。

NSObject对象的声明中除了Class isa;这个成员变量外,也还有其它的一些方法,只不过不存在于NSObject对象的存储空间。存处于其它对应的位置。

NSObject *obj = [[NSObject alloc] init];这句代码做了什么事情?
  1. alloc分配存储空间给NSObject对象
  2. 将存储空间的地址值(isa的地址值)赋值给obj指针进行存储。obj就可以指向NSObject对象。(obj指针存储的值就是NSObject对象中isa的地址值)

自定义类探究

上面研究了NSObject对象的本质,那么我们平时工作中创建的对象的本质是什么样的呢?

自定义一个Student类,直接写在main.m中。

代码语言:javascript
复制
@interface Student : NSObject
{
    @public
    int _no;
    int _age;
}
@end

@implementation Student

@end

将其转换成C++代码

代码语言:javascript
复制
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

我们可以发现在生成的C++文件内,Student类的实现是如下格式

代码语言:javascript
复制
struct Student_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _no;
    int _age;
};

这其中包含的struct NSObject_IMPL NSObject_IVARS;是指NSObject的实现,由于NSObject的实现里面只有一个Class isa;,所以NSObject_IMPL这个结构体占用的内存空间和单独一个isa占用的内存空间都是8个字节。

因此,上面的Student_IMPL结构体等价如下

代码语言:javascript
复制
struct Student_IMPL {
    Class isa;
    int _no;
    int _age;
};

所以,我们创建一个Student对象需要Class isa(8字节)+int _no(4字节)+int _age(4字节)=16字节

通过结构体访问属性

main.m中增加如下代码

代码语言:javascript
复制
struct Student_IMPL {
    Class isa;
    int _no;
    int _age;
};

用结构体去访问属性

代码语言:javascript
复制
Student *student = [[Student alloc] init];
student->_no = 4;
student->_age = 5;

struct Student_IMPL *studentImpl = (__bridge struct Student_IMPL *)student;
        
NSLog(@"_no = %d, _age = %d", studentImpl->_no, studentImpl->_age);

输出

代码语言:javascript
复制
_no = 4, _age = 5

由此说明,stundet指针指向的内存确实是Student_IMPL结构体。

通过运行时获取实例对象占用内存的大小

代码语言:javascript
复制
class_getInstanceSize()

我们同样可以通过上面的运行时方法获取到实例对象占用的内存大小

代码语言:javascript
复制
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
NSLog(@"%zd", class_getInstanceSize([Student class]));

输出结果仍为

代码语言:javascript
复制
NSobject = 8
Student = 16

窥探内存结构

通过DebugView Memory查看

调用Debug->Debug Workflow->View Memory

在下图Address位置输入stundent对象地址后,我们就可以看到其内存结构

由于现在计算机普遍都是小端模式,计算机读取地址的时候也是从高地址开始读取。因此,成员内存地址分别为

代码语言:javascript
复制
0x001D808001000011E9
0x00000004
0x00000005
通过lldb查看

po(print object)打印出student的地址

代码语言:javascript
复制
(lldb) po student
<Student: 0x10040fb20>

再进行内存读取memory read (address)

代码语言:javascript
复制
(lldb) memory read 0x10040fb20
0x10040fb20: e9 11 00 00 01 80 1d 00 04 00 00 00 05 00 00 00  ................
0x10040fb30: d0 fa 40 00 01 00 00 00 10 ee 40 00 01 00 00 00  ..@.......@.....

由此可以看到,和我们用DebugView Memory的内存结构是一样的。

拓展

x等价于memory read

代码语言:javascript
复制
(lldb) x 0x10040fb20
0x10040fb20: e9 11 00 00 01 80 1d 00 04 00 00 00 05 00 00 00  ................
0x10040fb30: d0 fa 40 00 01 00 00 00 10 ee 40 00 01 00 00 00  ..@.......@.....

以固定格式读取内存

4次、4个字节、16进制格式读取

代码语言:javascript
复制
(lldb) x/4xw 0x10040fb20
0x10040fb20: 0x000011e9 0x001d8001 0x00000004 0x00000005
  • printp:打印
  • po:打印对象
读取内存
  • memory read/数量、格式、字节大小 内存地址
  • x/数量、格式、字节大小 内存地址
  • 例如 : x/4xw 0x10040fb20
格式
  • x
    • 16进制
  • f
    • 浮点
  • d
    • 10进制
字节大小
  • b
    • byte 1字节
  • h
    • half word 2字节
  • w
    • word 4字节
  • g
    • giant word 8字节
修改内存中的值
  • memory write 0x1000000

比如我们想要修改Student类中_no = 4;的值

先查看内存地址

代码语言:javascript
复制
(lldb) x/16xb 0x10040fb20
0x10040fb20: 0xe9 0x11 0x00 0x00 0x01 0x80 0x1d 0x00
0x10040fb28: 0x04 0x00 0x00 0x00 0x05 0x00 0x00 0x00

注意此时0x10040fb28地址中第一个字节的值为4,下面进行修改

代码语言:javascript
复制
memory write 0x10040fb28 6

可以看到,_no的值被修改为6了。

更复杂继承关系的探究

Student->Person->NSObject这种继承关系的本质是怎样的呢?

代码如下

代码语言:javascript
复制
@interface Person : NSObject
{
    int _age;
}
@end

@implementation Person

@end

@interface Student : Person
{
    int _no;
}
@end

@implementation Student

@end

如果再求PersonStudent类分别占用多少内存空间呢?

代码语言:javascript
复制
NSLog(@"%zd", class_getInstanceSize([Person class]));
NSLog(@"%zd", class_getInstanceSize([Student class]));

结果会输出

代码语言:javascript
复制
16
16

下面我们来探究一下,将代码转成C++代码,我们可以看到Student对象的实现,和Person对象的实现。

Student对象的实现中包含了Person对象的实现,Person对象的实现包含了NSObject对象的实现。

因此,Person对象的实现等同于如下

代码语言:javascript
复制
struct Person_IMPL {
    Class isa;
    int _age;
};

isa占用8字节,_age占用4字节,根据结构体所占用内存空间大小并根据内存对其原则可知,Person对象占用16个字节。注意 : 不是12个字节

Student对象的实现

代码语言:javascript
复制
struct Student_IMPL {
    struct Person_IMPL Person_IVARS;
    int _no;
};

里面包含Person对象的实现(占16字节)和int _no(4字节),计算Student对象所占的字节数,同样还是16字节,因为Person里虽然占了16个字节,但是实际上有4个字节是空闲的,直接给Student对象的int _no用就可以了,不用再开辟新的存储空间了。注意 : 不能算成是20个字节

假如Student对象中还有一个int _height对象呢,Student占用多少内存空间呢?

答案是24个,还是根据内存对齐原则算出的。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.03.13 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一个 NSObject 对象占用多少内存?
    • Objective-C的对象、类主要是基于C/C++的什么数据结构实现的?
      • 将main.m中下面代码转成C++代码
        • 一个 OC 对象在内存中是如何布局的?
          • NSObject *obj = [[NSObject alloc] init];这句代码做了什么事情?
          • 自定义类探究
            • 通过结构体访问属性
              • 通过运行时获取实例对象占用内存的大小
                • 通过Debug的View Memory查看
                • 通过lldb查看
            • 窥探内存结构
            • 更复杂继承关系的探究
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档