OC对象的本质分析


NSObject实例对象占用的内存大小分析

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

下面的命令可以将Objective-C代码转换为CC++代码, 但是转换出来的代码仅供分析参考.

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的面向对象是基于CC++的结构体实现的.

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对象的内存分配过程:

  1. allocWithZone:
  2. _objc_rootAllocWithZone()
  3. class_createInstance()
  4. _class_createInstanceFromZone
  5. 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

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];

注意点

  1. Class objc_getClass(const char *aClassName)

    • 传入字符串类名
    • 返回对应的类对象
  2. Class object_getClass(id obj)

    • 传入的obj可能是instance对象、class对象、meta-class对象
    • 如果传入的是instance对象,返回class对象
    • 如果传入的是class对象,返回meta-class对象
    • 如果传入的是meta-class对象,返回NSObject(基类)的meta-class对象
  3. -(Class)class、+ (Class)class 返回的就是类对象

原文地址:https://www.cnblogs.com/junhuawang/p/13476881.html