A exploração subjacente de objetos iOS (abaixo)

O artigo anterior verificou que a ordem das variáveis ​​membro na estrutura é diferente, o que tem impacto na alocação de memória, tem o mesmo impacto nessa categoria? Vamos verificar, primeiro criamos uma classe de WTPerson, e instanciamos um objeto e atribuímos um valor a ele. Se a ordem da variável também afeta a alocação de memória, a memória do objeto deve ser como mostra a figura

@interface WTPerson : NSObject //(错误的分配方式)
@property (nonatomic, copy) NSString *name; // 8字节 从8开始 15结束
@property (nonatomic, assign) short age;    // 2字节 从16开始 17结束
@property (nonatomic, assign) int isFree;   // 4字节 18不能整除4 20开始 23结束
@property (nonatomic, copy) NSString *nickName; // 8字节 24开始 31结束
@property (nonatomic, assign) short sex;    // 2字节 32开始 33结束
+ (void)testMethod;
- (void)testMethod;
@end

WTPerson *p = [[WTPerson alloc] init];
p.name      = @"Vitus";
p.age       = 67;
p.sex = 1;
NSLog(@"对象至少需要的内存大小--%lu",class_getInstanceSize([p class])); //33
NSLog(@"系统分配的内存大小--%lu",malloc_size((__bridge const void *)(p))); //48
复制代码

Se a ordem das variáveis ​​realmente afetar a alocação de memória, ela deve imprimir 33 memórias necessárias e 48 memórias alocadas, mas o resultado real impresso é o seguinte

2022-04-19 10:45:22.985645+0800 KCObjcBuild[30642:791877] 对象至少需要的内存大小--32
2022-04-19 10:45:22.986840+0800 KCObjcBuild[30642:791877] 系统分配的内存大小--32
复制代码

Pode-se ver que a ordem das variáveis ​​no objeto não tem efeito no espaço de memória, e também verifica se o método não está realmente no espaço de memória alocado. Como é armazenado o valor deste atributo? Adicione mais alguns atributos ao imprima usando llvm, aqui As instruções p e po comuns que precisamos usar, assim como as x/nufinstruções. \

Vamos primeiro dar uma olhada em x/nuf:
x: representa a impressão hexadecimal
n: representa a impressão de n unidades de endereço
u: representa o comprimento de uma unidade de endereço (g representa 8 bytes b representa um byte h representa um byte duplo w representa 4 bytes)
f : representa a maneira de exibir variáveis ​​(x é exibido em hexadecimal, d é exibido em decimal, u é exibido em formato decimal, inteiro sem sinal é exibido em formato decimal, o é exibido em formato octal, t é exibido em formato binário e c é exibido em formato de caractere. f é exibido em formato de ponto flutuante)

t1.pngA partir da figura, podemos ver que o segundo endereço de memória armazena três valores 0x00000043 de idade, 0x01 de sexo e 0x01 de isFree. Isso mostra que, de fato, o sistema otimizou o alinhamento da memória no nível do objeto. Não importa como seus atributos estejam organizados, a otimização será realizada ao armazenar os dados na memória.
A memória de todos os objetos está otimizada? Vamos alterar a propriedade para uma variável de membro e examiná-la novamente

@interface WTPerson : NSObject {
    @public
    short sex;
    NSString *name;
    NSString *nickName;
    int age;
}
@end
2022-04-19 13:24:44.273136+0800 KCObjcBuild[35610:954602] 对象至少需要的内存大小--40
2022-04-19 13:24:44.274207+0800 KCObjcBuild[35610:954602] 系统分配的内存大小--48
复制代码

Neste momento, descobriremos que a memória não está otimizada para 32, portanto o sistema otimiza apenas as variáveis ​​de membro geradas pelo atributo e não otimiza as variáveis ​​de membro definidas por ele mesmo. No entanto, esse tipo de consumo de memória é muito pequeno, até 16 bytes, e 1024 bytes é apenas 1kb, o que é realmente insignificante, e agora todas as variáveis ​​de membro são geradas por atributos, e é muito raro escrever variáveis ​​de membro por conta própria.

é um

已经知道了影响内存的因素都有哪些,那我们再来聊一聊isa指针,我们知道所以的object都有一个8字节的isa指针,那isa指针到底是什么?到底有什么用? 在上一篇文章中我们知道创建对象的方法_class_createInstanceFromZone,在里面使用obj->initIsa(cls); 来初始化isa指针,在objc-object.h中找到相应方法,我们发现isa的类型是 isa_t newisa(0);在objc-private.h文件中发现其是union类型,也就是联合体。那我们先来了解一下什么是联合体。 t2.png 我们发现person1这个结构体可以获取三次赋值的所以值,而person2这个联合体每次赋值后其他的值都会被同步修改,这是因为联合体共用一块内存空间,打印他们的内存地址都是相同的 t3.png 共用的内存空间为可以容纳最大的成员变量,且是成员变量最大类型(基本数据类型)的整数倍。
结构体和联合体的区别:结构体中成员变量可以共存,联合体成员变量互斥,节省一定的内存空间。
了解了联合体后我们继续探索isa_t,其中struct { ISA_BITFIELD;  // defined in isa.h }; 这个代表isa_t的属性,我们查看一下ISA_BITFIELD都有什么。

# if __arm64__
......其他系统
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_HAS_CXX_DTOR_BIT 1
#   define ISA_BITFIELD                                                        
      uintptr_t nonpointer        : 1; //标识是否为nonpointer 
      uintptr_t has_assoc         : 1; //是否有关联对象
      uintptr_t has_cxx_dtor      : 1; //该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象
      uintptr_t shiftcls          : 44; // 存储对象的指针
      uintptr_t magic             : 6; //⽤于调试器判断当前对象是真的对象还是没有初始化的空间
      uintptr_t weakly_referenced : 1; //指对象是否被指向或者曾经指向⼀个ARC的弱变量,没有弱引⽤的对象可以更快释放
      uintptr_t unused            : 1;
      uintptr_t has_sidetable_rc  : 1; //当对象引用计数大于extra_rc所能存储的最大范围时,需借用该变量
      uintptr_t extra_rc          : 8 //当表示该对象的引⽤计数值,实际上是引⽤计数值减 1
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)
# endif
复制代码

我们可以看到ISA_BITFIELD内部的成员都是使用的: 1这种形式,这种方式是位域的结构,那我们来简单了解一下位域。 t4.png 正常来说,一个这样的结构体需要占用3个字节来表示所存储的数据,但是当使用了位域了以后,我们只需要2个字节就能够把内容给存储下来(a占第一个字节的3位,b需要6个bit,放在第一个字节中会超出字节,所以开辟第二个字节放入6位,c放在第二个字节的第7位),因此位域的作用,也是为了让内存更加优化。、 现在isa的数据结构已经了解了,那接下来看看initIsa的赋值操作

if (!nonpointer) { 
    newisa.setClass(cls, this); 
} else { 
    ...... 对位域的一系列赋值 ......
    newisa.setClass(cls, this); 
} 
isa = newisa;
复制代码

我们可以看到,当nonpointer为0的时候,直接绑定类和地址的对应关系,而当nonpointer为1的时候,除了保存类的信息以外,还会保存一些额外的特殊信息,我们称之为NonpointerIsa。isa其实并不单单是一个指针,以x86_64架构为例,实际上有44位用于存储对象地址。其余位用来存储一些特殊的值。
既然isa中不只有类信息,其中还存在别的特殊信息,那我们怎么屏蔽其他的特殊信息,直接找到类信息呢?
第一种方式就是使用源码种提供的ISA_MASK进行掩码 0x011d800100008a61 & 0x00007ffffffffff8ULL就可以获取到类名
第二种方式是对isa指针进行位运算,我们知道对象地址存储在44位上,前面3位和后面17位存储的是特殊的值,那我们只需要清除前面3位和后面17位的值,就能拿到我们需要的类名。
即0x011d800100008a61 >> 3 << 20 >> 17,同样可以获取类名,同理也可以通过位运算获取该对象的引用计数,右移56位即可。 t6.png 到这里isa的探索暂告段落,isa最主要的作用是记录对象指针地址,指针地址用不到64位这么大的内存,所以同步记录对象的相应状态来优化内存和优化对对象的操作判断。

initIsa后在_class_createInstanceFromZone中已完成对象的创建和指针地址绑定,alloc流程就已经完成,我们就获取到了一个对象,我们也知道init中只是return self;方便我们进行扩展initWith..,那创建对象常写的第三个关键字new到底是如何创建的对象呢?

Em NSObject.m, encontramos + ( id ) new { return [callAlloc( self , false / checkNil /) init]; }, sabemos que alloc também chama callAllocmétodos, então podemos pensar que [ class new] é realmente equivalente em [[class alloc] init].

Resumir

A criação do objeto é feita por alloc, em que o endereço de memória alocado é alocado de acordo com um múltiplo inteiro de 16 bytes, e o espaço de memória do objeto é aberto primeiro, e então o espaço de memória e o objeto são vinculados quando o No processo, aprendemos também como são armazenados os valores dos atributos do objeto, o alinhamento da estrutura, a otimização da união e o campo de bits para o espaço de memória, o que o ponteiro isa armazena, e como usar o ponteiro isa para obter o endereço de memória. Escreva mais propriedades, é melhor não escrever variáveis ​​de membros, afinal, o sistema de propriedades vai te ajudar a otimizar a memória.

Acho que você gosta

Origin juejin.im/post/7088248432879468575
Recomendado
Clasificación