学习MJ的视频课程,整理总结知识点–OC对象的本质
[TOC]
Objective-C是一种通用、高级、面向对象的编程语言。它扩展了标准的ANSI C编程语言,将Smalltalk式的消息传递机制加入到ANSI C中。当前主要支持的编译器有GCC和Clang(采用LLVM作为后端)维基:Objective-C
Objective-C和C_C++
想探究OC对象的本质,我们可以逐步深入。我们知道,最了解一个编程语言的,莫过于它的编译器了,而我们知道Xcode内置的编译器的前端是clang
,clang是一个C、C++、Object-C
的轻量级编译器。Objective-C
兼容C
语言,我们可以通过clang编译器将Objective-C代码重写成更基础的C/C++代码,这样就能看到Object-C在C/C++下的等价实现。但是使用Xcode
构建ObjC项目时并不会先将Objective-C代码翻译为C++,这是因为clang编译器支持直接编译Objective-C代码。
(本文为了书写方便,将Object-C
简写为OC
)
Objective-C对象数据结构
我们知道OC是基于C语言扩展出来的,C是没有对象的概念,那么OC的对象是基于什么结构实现的呢?
或许我们曾经通过Xcode看过objc.h的头文件,发现如下代码
1 | /// Represents an instance of a class. |
同时我们知道,结构体里面可以存放不同类型的数据。我们或许猜测到NSObject
的底层结构是基于结构体。
事实上OC对象确实是基于结构体的实现,下面我们通过重写源码进行验证。
我们以简单main.m函数代码为例,新建一个如下图所示的工程,然后通过clang
命令把main函数及内部的NSObject
重写为C++的实现,即可了解objc对象的实现。
1 | int main(int argc, const char * argv[]) { |
终端切换到main.m所在的目录,然后执行clang命令,如下:
(关于命令的使用我们可以通过clang --help
来了解)
1 | clang -rewrite-objc main.m -o main.cpp |
打开main.cpp
文件,如下图,我们在最后看到main.m
函数的实现,同时还可以一窥OC的消息机制
1 | int main(int argc, const char * argv[]) { |
同时,我们在main.cpp
可以看到这样的一段代码
1 | // NSObject_IMPL (NSObject_IMPLEMENTATION) |
即说明了NSObject对象是通过结构体实现的,且内部只有一个Class(结构体)指针。
NSObject的内存本质
通过上面的内容,我们已经知道NSObject对象的底层实现是结构体,且内部只有一个isa结构体指针,这个指针指向Class的起始地址。
class_getInstanceSize、malloc_size
知道了NSObject的内存结构后,接下来我们探讨一个NSObject对象占用多少内存?
思路:通过上面的小节,我们已经知道NSObject对象内部只有一个isa结构体指针,我们知道在64位中操作系统中,指针占用内存空间是8个字节,所以我们推测一个NSObject对象应该只占8个字节,事实是否如此?我们可以利用系统提供的malloc_size
函数进行验证。
通过系统提供的函数即可获得,如下:
1 | int main(int argc, const char * argv[]) { |
更进一步,我们可以研究class_getInstanceSize、malloc_size
这两个函数的实现,因为苹果已经开源了objc,我们可以查找相应的实现代码从而做到知其所以然。
苹果的源码开放地址为:Source Browser,如果想研究苹果的源码一般都是访问这个地址。我们下载objc4的最新源码,然后解压打开源码。搜索我们要找的源码class_getInstanceSize
,结果如下:
1 | size_t class_getInstanceSize(Class cls) |
我们看到alignedInstanceSize
函数的实现如下:
1 | // Class's ivar size rounded up to a pointer-size boundary. |
从而得出结论:class_getInstanceSize
是获得NSObject实例对象的成员变量所占用的空间大小(isa指针所占的空间),占用的空间大小8字节。
但是我们通过malloc_size
获取到的空间大小是16字节,为什么?
接下来我们探究malloc_size
的实现。我们知道malloc_size
是调用的alloc
方法,而alloc
会调用allocWithZone
,我们在源码中搜索allocWithZone
,会在NSObject.mm
文件中看到类似如下的实现
1 | // Call [cls alloc] or [cls allocWithZone:nil], with appropriate |
我们研究_objc_rootAllocWithZone
的实现会发现如下调用顺序:_objc_rootAllocWithZone
->_class_createInstanceFromZone
我们搜索_class_createInstanceFromZone
的实现,会有如下发现:
1 | static ALWAYS_INLINE id |
然后发现关键的地方,变量size的值是通过instanceSize
函数返回的,我们查找instanceSize
的源码,然后会有如下发现
1 | size_t instanceSize(size_t extraBytes) const { |
上面的源码有两个关键的地方
alignedInstanceSize
我们在上面已经知道它返回的是成员变量所占用的空间大小,NSObject对象占用的大小为8字节。// CF requires all objects be at least 16 bytes.
。
我们可以知道分配的size一旦小于16,CoreFoundation框架会返回最小值16。
至于alignedInstanceSize
函数以及CF框架为什么会是16,这里不再深究,感兴趣的可以搜索内存对齐
。这里简述一下:内存对齐(数据结构对齐)是方便访问,OC对象占用的空间大小都是16的倍数
,结构体的内存对齐最小单元是8字节
这里做一下总结:
系统分配了16个字节给NSObject对象(通过malloc_size函数获得),但是NSObject内部只占用了8个字节的空间(64为环境下,可以通过class_getInstanceSize函数获得)
实时查看内存数据
View Memory
了解一个对象所占用内存具体情况,我们可以利用Xcode的菜单栏上的Debug
-> Debug Workfllow
-> View Memory
,来查看一个对象的内存占用情况。
备注:内存中0000…一般代表未使用的空白空间
LLDB
我们还可以通过Xcode提供的LLDB
进行断点调试
1 | print、p:打印地址 |
读取内存 memory read
(简写为x
)
例:x/4xw 0x100550c20
1 | memory read/数量格式字节数 内存地址 |
修改内存中的值 memory write
例: memory write 0x100550c29 10
1 | memory write 内存地址 数值 |
自定义对象(Student)的本质
我们研究NSObject对象后,继续研究简单的自定义对象,以Student
对象为例,我们可以根据NSObject的内存结构推断出下面的结果。我们同样可以使用clang对这里的OC源码重新进行验证。
1 | struct Student_IMPL { |
Student的内存布局
Student对象占用了16个字节的内存空间,前8个字节放isa指针,接下来4位放_no变量,最后4位放_age变量。
我们还可以通过View Memory
验证,以及通过LLDB重写内存验证这一结论。
通过上面的验证,可以知道Student的真实结构如下
更复杂的继承结构
结论:Person对象占用了16个字节的内存空间,Student也占用了16个字节的内存空间。
备注:Student对象的Person_IVARS其实占用了16个字节的内存空间,但是有4个字节的未使用空间,_no变量大小为4个字节,刚好利用这4个内存空间。
我们可以通过malloc_size
函数进行验证这个结论
属性和方法
当我们为Person类增加一个属性时,如下:
1 | @interface Person : NSObject |
我们知道@property
默认会生成一个带下划线的变量,以及对应属性set和get方法,我们再次通过clang命令编译重写导出后能看到如下代码
1 | struct Person_IMPL { |
也即验证了@property可以生成对应的变量。
关于@property生成的方法的细节,我们在后面的文章中再讲解
小结
- OC对象的本质是结构体
- 系统分配了16个字节给NSObject对象,但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)
class_getInstanceSize
返回的是需要的最小空间,malloc_size
返回的是实际分配的内存空间大小
参考和源码
Apple Source Browser - objc