从OC开始底层学习(2)
一、对象的内存分布和影响对象内存的因素
我们先创建一个类,并为其分配属性。
@interface LGPerson : NSObject @property (nonatomic ,copy) NSString *name; @property (nonatomic ,copy) NSString *hobby; @property (nonatomic ,assign) int age; @property (nonatomic ,assign) double hight; @property (nonatomic ,assign) short number; @end
然后我们为其赋值,并打印系统为其分配的内存,输出结果为48,那如果我们为LGPerson添加方法呢?对象的内存是否会发现变化?显然会不会的,因为对象的内存中存储的是isa+成员变量的值,除此之外的其他不会对对象的内存产生影响。
LGPerson *p = [LGPerson new]; p.name = @"三夏"; p.hobby = @"girl"; p.hight = 1.80; p.age = 18; p.number = 123; NSLog(@"%lu",malloc_size((__bridge const void *)(p)));
那么在对象的内存中属性存储的值是按照什么顺序存储的呢?有人想可能是按照对象的赋值顺序,也有可能是按照属性的初始化顺序?其实对象中的属性存储的顺序是苹果自动重排的,系统这这么做的目的是为了优化内存,就比如这里,int(4),char(1),short(2),他们完全可以在同一个8字节的内存中存储来达到优化内存的目的。那如果类被继承,他的属性的排序又是如何的呢?这里需要注意苹果只会重排属性,而不会重排成员变量,而且重排的时候只会考虑当前类,当前类不会考虑父类
二、联合体和位域
2.1 联合体
union LGTeacher2 { char *name; int age; int height; }t2; union LGTeacher3 { char a[7]; //7 int b; // 4 }t3; // 8 int main(int argc, char * argv[]) { NSString * appDelegateClassName; @autoreleasepool { // Setup code that might create autoreleased objects goes here. appDelegateClassName = NSStringFromClass([AppDelegate class]); t2.name = "三夏"; t2.age = 18; t2.height = 2.2; NSLog(@"%p %p %p",&t2,&t2.age,&t2.height); NSLog(@"%lu",sizeof(t2)); NSLog(@"%lu",sizeof(t3)); } }
在这里,t2和t3都是联合体的表达形式,联合体又叫共用体,union就是在内存中划了一个足够用的空间(这块空间是共用的),系统都不管这块空间里是怎么存储的,联合体的成员变量就相当于为这块内存空间开辟了几个访问途径,他们共享这一块内存。
- 联合体的成员共用一块内存空间,一次只能使用一个成员
- 联合体可以定义多个成员,每个成员都是访问这个联合体的一条路径
- 对某一个成员赋值,会覆盖其他成员的值
- 可以节省一定的内存空间
这块内存是如何分配的?
- 联合体大小必须能容纳联合体中最大的成员变量
-
通过上面计算出的联合体大小必须是联合体中占内存大小(最大的基本数据类型)大小的整数倍,比如char a[7] 占了7个字节,但是他的基本数据类型就是char,是一个字节。
2.2 联合体 和 结构体之间的区别
- 结构体(struct)中所有变量是“共存”的,而联合体(union)中是各变量是“互斥”的,只能存在一个
- (结构体全分配,联合体每次仅分配一个)struct内存空间的分配是粗放的,不管用不用,全部分配。这样带来的一个坏处就是对于内存的消耗要大一些。但是结构体里面的数据是完整的。 而联合体里面的数据只能存在一个,但优点是内存使用更为精细灵活,也节省了内存空间。
2.3 位域
struct LGStruct2 { // a: 位域名 32:位域长度 int a : 32; char b : 2; char c : 7; char d : 2; }struct2;
这个是位域的写法,可以节省内存空间,关于位域有几点说明:
- 位域的类型说明符是 位域名:位域长度
- 位域的长度不能超过数据类型的最大长度
- 一个位域是存储在同一个字节当中的,如果这一个字节所剩的空间不够去存放另一个位域的时候, 另一个位域就会从下一个字节开始存放
struct LGStruct2 { // a: 位域名 7:位域长度 char a : 7;//占7位 char b : 2;//从上个字节所剩空间位1位,不够存放,则需要从下一个字节开始存放 char c : 7;//从下一个字节开始存放 char d : 2;//从下一个字节开始存放 }struct2;
三、nonPointerIsa
在上次的alloc探索中,我们看到了_class_createInstanceFromZone方法内的initInstanceIsa方法,这个方法是把创建的对象通过对象内的isa指针来关联到相应的类,isa指针内包含了对象所属的类对象的内存地址。现在我们接着往下看:
我们发现在initInstanceIsa方法调用了initIsa方法,我们追根溯源发现了其内部就是对对象的isa指针进行初始化,同时我们发现了isa_t的数据类型。
isa_t就是一个联合体,目的是为了兼容之前的版本,现在系统使用的isa是nonPointIsa,
nonPointerIsa是内存优化的一种手段。isa是一个Class类型的结构体指针,占8个字节,主要是用 来存内存地址的。但是8个字节意味着它就有8*8=64位。存储地址根本不需要这么多的内存空间。 而且每个对象都有个isa指针,这样就浪费了内存。所以苹果就把和对象一些息息相关的东⻄,存 在了这块内存空间里面。这种isa指针就叫nonPointerIsa。
四 isa的数据结构
对其中一些符号的解释说明如下:
五 补充知识
p : <值类型> 引用 值 po:值 p/x : 以16字节输出 p/o:以8字节输出 p/t :以2字节输出 p/f:以浮点输出 x:输出内存地址 x/4gx:以16进制的形式打印4个地址