iOS-OC对象的本质分析
NSObject实例对象占用的内存大小分析
将Objective-C转换为C\C++代码
下面的命令可以将Objective-C代码转换为C\C++代码, 但是转换出来的代码仅供分析参考.
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o mainarm64.cpp
如果需要链接其他框架,使用-framework参数。比如-framework UIKit.
使用上面的命令可以窥探出NSObject的底层实现如下:
1 //头文件定义 2 @interface NSObject <NSObject> { 3 Class isa; 4 } 5 6 //转换为C++代码后 7 struct NSObject_IMPL { 8 Class isa; //64位系统中指针占8个字节 9 };
可见,Objective-C的面向对象是基于C\C++的结构体实现的.
NSObject实例对象的内存分配
导入 objc/runtime.h
后可以使用函数class_getInstanceSize
获得NSObject实例对象的成员变量所占用的大小为8个字节(内存对齐后的大小).
class_getInstanceSize([NSObject class])
导入 malloc/malloc.h
后可以使用函数malloc_size
获得NSObject实例对象指针指向内存的大小为16个字节.
malloc_size((__bridge const void *)(obj))
通过以下路径追踪NSObject对象的内存分配过程:
- allocWithZone:
- _objc_rootAllocWithZone()
- class_createInstance()
- _class_createInstanceFromZone
- instanceSize()
其中instanceSize()函数返回要为NSObject实例对象分配的内存大小, 其实现如下:
1 size_t instanceSize(size_t extraBytes) { 2 size_t size = alignedInstanceSize() + extraBytes; 3 // CF requires all objects be at least 16 bytes. 4 if (size < 16) size = 16; 5 return size; 6 }
可见NSObject实例对象占用的最小内存为16个字节.
使用Xcode查看内存数据
实时查看内存数据
在Xcode菜单栏中通过以下顺序可以查看对象的内存数据
Debug -> Debug Workfllow -> View Memory
对一个NSObject实例对象来说,通过该方法观察到前8个字节为非0值,后8个字节为全为0,第17位开始为非0值。 由此可以推断,前8个字节代表isa指针,也从侧面验证了一个NSObject实例对象指针指向内存的大小为16个字节。
常用的lldb指令
print用于打印指针地址,可缩写为p.
po
po用于打印对象,即print object.
memory read
- memory read用于读取内存,可以缩写为x。其使用方法为:
x/数量|格式|字节数 内存地址
数量参数说明:
要打印多少个数据,格式参数和字节参数说明怎么样打印这些数据。这些参数都可以省略,即直接使用memory read 内存地址
.
格式参数说明:
x是16进制,f是浮点,d是10进制
字节参数说明:
b:byte 1字节,h:half word 2字节 w:word 4字节,g:giant word 8字节
例如,x/3xg表示以16进制的形式打印3个字节串,每个字节串包含8个字节。
memory write
memory write用于修改内存中的值. 使用方法为:
memory write 内存地址 新值
继承关系的内存布局
定义如下两个类:
1 @interface Person : NSObject 2 @property (nonatomic,assign) int age; 3 @end 4 5 @implementation Person 6 @end 7 8 @interface Student : Person 9 @property (nonatomic,assign) int no; 10 @end 11 12 @implementation Student 13 @end
将其转换为C++代码可见其底层实现如下:
1 struct NSObject_IMPL { 2 Class isa; 3 }; 4 5 struct Person_IMPL { 6 struct NSObject_IMPL NSObject_IVARS; 7 int _age; 8 }; 9 10 struct Student_IMPL { 11 struct Person_IMPL Person_IVARS; 12 int _no; 13 };
可见子类实例对象的结构体中会包含父类的结构体,且父类的结构体存储在最前。
子类创建的实例其内存大小的分析
定义如下一个类:
1 @interface Person : NSObject 2 @property (nonatomic,assign) int height; 3 @property (nonatomic,assign) int age; 4 @property (nonatomic,assign) int no; 5 @end 6 7 @implementation Person 8 @end
将其转换为C++代码:
1 struct NSObject_IMPL { 2 Class isa; 3 }; 4 5 struct Person_IMPL { 6 struct NSObject_IMPL NSObject_IVARS; //8个字节 7 int _height; //4个字节 8 int _age; //4个字节 9 int _no; //4个字节 10 };
根据分析可知,Person_IMPL结构体内所有成员的总大小为20个字节,但由于内存对齐的原因,Person_IMPL结构体占用的内存大小应为24个字节。
实际上,使用class_getInstanceSize
打印的结果为24,但是使用malloc_size
打印的结果为32. 可见,Person实例对象实际占用的内存大小为32字节,尽管其只使用了24个字节。
alloc方法底层调用的方法为allocWithZone, 而allocWithZone方法则会调用calloc函数分配内存. 相关的源码可以在https://opensource.apple.com/tarballs/libmalloc/进行下载。
根据源码可以发现,系统为了优化内存的使用效率,规定iOS中分配的内存大小都必须为16的倍数.
OC对象的分类
OC中的对象,主要可以分为三类:
-
instance对象(实例对象)
-
class对象 (类对象)
-
meta-class对象 (元类对象)
instance对象(实例对象)
instance对象(实例对象)就是通过类alloc出来的对象,每次类调用alloc都会生成一个新的instance对象。instance对象在内存中存储的主要信息有:
- isa指针
- 其他类的成员变量(值)
1 NSObject *obj1 = [[NSObject alloc] init]; 2 NSObject *obj2 = [[NSObject alloc] init];
源码如下:
#include <objc/objc.h>
1 /// An opaque type that represents an Objective-C class. 2 typedef struct objc_class *Class; 3 4 /// Represents an instance of a class. 5 struct objc_object { 6 Class _Nonnull isa OBJC_ISA_AVAILABILITY; 7 };
class对象 (类对象)
obj1和obj2就是两个NSObject的实例对象,分别占据两块的不同的内存。
每个类在内存中有且只有一个class对象(类对象),class对象在内存中存储的主要信息有:
- isa指针
- superclass指针
- 类的属性信息(@property)
- 类的对象方法信息(instance method)
- 类的协议信息(protocol)
- 类的成员变量信息(类型,名称)
- ........
源码如下:
#include <objc/runtime.h>
1 struct objc_class { 2 Class _Nonnull isa OBJC_ISA_AVAILABILITY; 3 4 #if !__OBJC2__ 5 Class _Nullable super_class OBJC2_UNAVAILABLE; 6 const char * _Nonnull name OBJC2_UNAVAILABLE; 7 long version OBJC2_UNAVAILABLE; 8 long info OBJC2_UNAVAILABLE; 9 long instance_size OBJC2_UNAVAILABLE; 10 struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; 11 struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; 12 struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; 13 struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; 14 #endif 15 16 } OBJC2_UNAVAILABLE;
下面代码获取到的都是NSObject类对象:
1 NSObject *obj1 = [[NSObject alloc] init]; 2 NSObject *obj2 = [[NSObject alloc] init]; 3 Class objClass1 = [obj1 class]; 4 Class objClass2 = [obj2 class]; 5 Class objClass3 = [NSObject class]; 6 Class objClass4 = object_getClass(obj1); 7 Class objClass5 = object_getClass(obj2); 8 Class objClass6 = objc_getClass("NSObject");
meta-class对象 (元类对象)
每个类在内存中有且只有一个meta-class对象(元类对象)。meta-class对象和class对象的内存结构是一样的,但是用途不一样。meta-class对象在内存中存储的主要信息有:
- isa指针
- superclass指针
- 类方法信息(class method)
- ……
下面代码获取到的metaClass就是NSObject的meta-class对象:
Class metaClass = object_getClass([NSObject class]);
需要注意的是,以下代码获取到的是Class对象,并不是meta-class对象:可以通过class_isMetaClass
方法判断一个Class对象是否为meta-class对象。
1 Class objClass = [[NSObject class] class];
注意点
-
Class objc_getClass(const char *aClassName)
- 传入字符串类名
- 返回对应的类对象
-
Class object_getClass(id obj)
- 传入的obj可能是instance对象、class对象、meta-class对象
- 如果传入的是instance对象,返回class对象
- 如果传入的是class对象,返回meta-class对象
- 如果传入的是meta-class对象,返回NSObject(基类)的meta-class对象
-
-(Class)class、+ (Class)class 返回的就是类对象
OC的类信息存放在哪里?
对象方法、属性、成员变量、协议信息,存放在class对象中
类方法,存放在meta-class对象中
成员变量的具体值,存放在instance对象