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/nuf
instruçõ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)
A 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类型,也就是联合体。那我们先来了解一下什么是联合体。 我们发现person1这个结构体可以获取三次赋值的所以值,而person2这个联合体每次赋值后其他的值都会被同步修改,这是因为联合体共用一块内存空间,打印他们的内存地址都是相同的
共用的内存空间为可以容纳最大的成员变量,且是成员变量最大类型(基本数据类型)的整数倍。
结构体和联合体的区别:结构体中成员变量可以共存,联合体成员变量互斥,节省一定的内存空间。
了解了联合体后我们继续探索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这种形式,这种方式是位域的结构,那我们来简单了解一下位域。 正常来说,一个这样的结构体需要占用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位即可。 到这里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 callAlloc
mé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.