Modelo de Objetos e Alocação de Memória
O runtime do Objective-C gerencia todas as instâncias e definições de classe através de estruturas de memória estritamente padronizadas. No nível mais básico, toda instância de um objeto é representada pela estrutura objc_object, que contém exclusivamente um campo isa. A própria classe é mapeada como um objeto especial, definido por objc_class, que herda de objc_object e estende seu layout com campos de despacho, cache e ponteiros de dados.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
struct objc_class : objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;
};
Navegação via Ponteiro isa e Cadeias de Herança
Em arquiteturas de 64 bits (ARM64/x86_64), o campo isa não armazena um ponteiro bruto. Ele utiliza uma técnica de bit packing, onde parte dos bits representa o endereço da classe ou metaclass e outra parte carrega flags de controle do runtime. Para recuperar o endereço real, o runtime aplica uma operação de máscara (ISA_MASK) seguida de um deslocamento à esquerda.
A cadeia de navegação segue um padrão rígido:
- A instância aponta seu
isapara a classe correspondente. - A classe aponta seu
isapara a metaclass. - A metaclass aponta seu
isa para a <strong>root metaclass</strong> (geralmenteNSObject). - A root metaclass aponta seu
isapara si mesma, fechando o ciclo de ponteiros.
Paralelamente, existe a cadeia de herança (superclass). Uma subclasse herda da classe pai, que herda da classe raiz. A classe raiz (NSObject) possui sua superclass definida como nil. Na dimensão das metaclasse, a sub-metaclasse herda da metaclasce da classe pai, e a root metaclass herda diretamente da classe raiz NSObject. É importante ressaltar que instâncias isoladas não participam de herança; essa hierarquia existe exclusivamente entre estruturas de tipo.
Essa topologia define onde os métodos são armazenados: métodos de instância residem no armazenamento de dados da classe, enquanto métodos de classe (estáticos) são persistidos no armazenamento de dados da respectiva metaclass.
Estrutura de Cache e Otimização
O campo cache (cache_t) acelera a resolução de métodos armazenando pares de seletores e implementações frequentemente acessados. Sua estrutura varia conforme a estratégia de armazenamento da máscara, mas em sistemas __LP64__, o alinhamento de memória garante um tamanho fixo de 16 bytes. A estrutura combina ponteiros para buckets, máscaras de tamanho e indicadores de ocupação, minimizando o impacto de alinhamento.
Gerenciamento de Dados via bits: ro, rw e rw_ext
O campo bits em objc_class é um wrapper que, após deslocamento de 32 bytes em relação ao endereço base da classe, aponta para estruturas de gerenciamento de dados. O runtime evoluiu para separar informações estáticas (definidas em tempo de compilação) das dinâmicas (modificáveis em execução).
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t *ivarLayout;
const char *name;
method_list_t *baseMethodList;
protocol_list_t *baseProtocols;
const ivar_list_t *ivars;
const uint8_t *weakIvarLayout;
property_list_t *baseProperties;
};
struct class_rw_ext_t {
method_list_t *attachedMethods;
protocol_list_t *attachedProtocols;
property_list_t *attachedProperties;
ivar_list_t *extendedIvars;
};
struct class_rw_t {
uint32_t flags;
uint16_t witness;
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
const method_array_t methods() const {
auto container = get_ro_or_rwe();
if (container.is<class_rw_ext_t *>()) {
return method_array_t{container.get<class_rw_ext_t *>()->attachedMethods};
}
return method_array_t{container.get<const class_ro_t *>()->baseMethodList};
}
const property_array_t properties() const {
auto container = get_ro_or_rwe();
if (container.is<class_rw_ext_t *>()) {
return property_array_t{container.get<class_rw_ext_t *>()->attachedProperties};
}
return property_array_t{container.get<const class_ro_t *>()->baseProperties};
}
};
class_ro_t (Read-Only) é alocada durante a compilação. Contém o layoutt base, nome, tamanho, variáveis de instância (ivars), e as listas iniciais de métodos e propriedades. O acesso à lista de variáveis de instância tipicamente exige um deslocamento de 48 bytes do início da estrutura em ambientes de 64 bits.
Quando uma classe é carregada no runtime (realizeClass), o sistema aloca um class_rw_t (Read-Write). Essa estrutura permite modificações em tempo de execução, como injeção de métodos via categorias ou manipulação direta da API. Para otimizar a memória, já que a maioria das classes nunca é alterada dinamicamente, o runtime extrai apenas os componentes mutáveis para um class_rw_ext_t (Read-Write Extension). Isso reduz drasticamente a pegada de memória das classes que não utilizam categorias ou métodos dinâmicos.
Exemplo de Inspeção de Estrutura
Para validar a distribuição de dados, considere a seguinte definição de classes:
@interface VeiculoBase : NSObject
{
NSString *placaIdentificadora;
}
@property (nonatomic, copy) NSString *modeloFabrica;
- (void)acelerarMotor;
+ (void)registrarRecall;
@end
@implementation VeiculoBase
- (void)acelerarMotor {}
+ (void)registrarRecall {}
@end
@interface CarroEletrico : VeiculoBase
@end
@implementation CarroEletrico
@end
Ao inspecionar o bits de VeiculoBase, o método methods() retornará uma matriz contendo acelerarMotor e os accessors sintetizados para modeloFabrica. A variável placaIdentificadora não aparece na lista de métodos; ela está armazenada exclusivamente no campo ivars dentro de class_ro_t. Se um método adicional for injetado via categoria, ele será alocado na attachedMethods de class_rw_ext_t, enquanto o método de classe registrarRecall será localizado ao inspecionar o bits da metaclass de VeiculoBase, comprovando a separação física entre métodos de instância e de classe.