从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个地址

 

posted on 2022-04-21 22:03  suanningmeng98  阅读(31)  评论(0编辑  收藏  举报