[Resumen de OC orientado a objetos + gestión de memoria + tiempo de ejecución]

Prefacio

Orientado a objetos + gestión de memoria + se agregará un resumen del tiempo de ejecución en cualquier momento, principalmente un resumen, omitiendo algunas implementaciones del código fuente.

orientado a objetos

1.1 ¿Cuánta memoria ocupa un objeto NSObject?

Referencia: Resumen de los principios subyacentes de MJ-iOS] ¿Cuánta memoria ocupa un objeto NSObject?
Al verlo en el compilador, hay dos formas de ver el tamaño.

  方式一:class_getInstanceSize([NSObject class])
     方式二:malloc_size((__bridge const void *)(obj))
// 一个NSObject对象占用多少内存
- (void)testObj {
    
    
    NSObject *obj = [[NSObject alloc] init];
    NSLog(@"class_getInstanceSize = %zu", class_getInstanceSize([NSObject class]));
    
    NSLog(@"malloc_size = %zu", malloc_size((__bridge const void *)(obj)));
    /*

     */
}

Por favor agregue la descripción de la imagen.
class_getInstanceSize([NSObject class])Lo que significa es: obtener el tamaño ocupado por las variables miembro del objeto de instancia de NSObject, no el tamaño ocupado por NSObject.
malloc_size((__bridge const void *)(obj))Significa: obtener el tamaño de la memoria a la que apunta el puntero obj.

Conclusión: Entonces, un NSObjectobjeto ocupa 16 bytes, pero en realidad solo se utilizan 8 bytes. El sistema asigna 16 bytes al objeto NSObject (obtenido mediante la función malloc_size), pero NSObjectsolo se utilizan 8 bytes de espacio dentro del objeto.
Insertar descripción de la imagen aquí

Principio: la capa inferior de OC determina que a un objeto se le asignan al menos 6 bytes al realizar la asignación. Si tiene menos de 16 bytes, se ve obligado a asignar 6 bytes al objeto.
Por favor agregue la descripción de la imagen.

1.2 Cadena de herencia de iOS y ¿adónde apunta el puntero del objeto?

La cadena de herencia se divide en tres partes: cadena de herencia de clases, cadena de herencia de metaclases y puntero isa.

@implementation ViewController

- (void)viewDidLoad {
    
    
    [super viewDidLoad];
    // Do any additional setup after loading the view.
//    NSLog(@"类继承探究:");
//    [self testSuperClass:Stu.class];
//    [self testSuperClass:Person.class];
//    [self testSuperClass:NSObject.class];
//
    NSLog(@"元类继承探究:");
    [self testMetaClass:Stu.class];
    [self testMetaClass:Person.class];
    [self testMetaClass:NSObject.class];
    
}


// 元类继承链
/*
 元类是系统自动创建的,和关联类同名。
 对象的isa指向类,类对象的isa指向元类
 */
-(void)testMetaClass: (id) class {
    
    
    Class cls = class;
    Class metaClass = object_getClass(cls);
    NSLog(@"类:%@_%p",cls,cls);
    NSLog(@"元类:%@_%p",metaClass,metaClass);
    [self testSuperClass:metaClass];
    // 观察打印结果知道,父类的元类 = 元类的父类(根类NSObject除外)
    
    // 根元类的父类 = 根类
}
// 继承链
- (void)testSuperClass:(id) class {
    
    
    Class cls = class;
    Class superClass = class_getSuperclass(cls);
    Class rootSuperClass = class_getSuperclass(superClass);
    NSLog(@"类:%@_%p",cls,cls);
    NSLog(@"父类:%@_%p",superClass,superClass);
    NSLog(@"父类:%@_%p",rootSuperClass,rootSuperClass);
    NSLog(@"----------");
}
@end

Por favor agregue la descripción de la imagen.
Resumen de los puntos clave de la cadena de herencia de metaclases:
1. La metaclase es creada automáticamente por el sistema y tiene el mismo nombre que la clase asociada.
2. El isa del objeto apunta a la clase y el isa del objeto de clase apunta a la metaclase

3. La clase padre de la metaclase == la metaclase de la clase padre (excepto la clase raíz)
3. El padre clase de la metaclase raíz == la clase raíz misma.

Resumen del puntero isa:

  • La isa del objeto apunta a la clase.
  • El isa de la clase apunta a la metaclase.
  • El isa de la metaclase apunta a la metaclase raíz.
  • El isa de la metaclase raíz apunta a la metaclase raíz.
-(void) testSuperIsa:(id) obj {
    
    
    Class isa = object_getClass(obj);
    Class metaIsa = object_getClass(isa);
    Class rootMetaIsa = object_getClass(metaIsa);
    NSLog(@"对象:%@_%p",obj,obj);
    NSLog(@"对象的isa-->%@_%p",isa,isa);
    NSLog(@"类的isa-->%@_%p",metaIsa,metaIsa);
    NSLog(@"元类isa-->%@_%p",rootMetaIsa,rootMetaIsa);
    NSLog(@"----------");
}

Por favor agregue la descripción de la imagen.

  • La siguiente figura puede resumir lo anterior.
    Por favor agregue la descripción de la imagen.

1.3 ¿Dónde se almacena la información de la clase OC? -isa puntero

En Objective-C, cada objeto tiene un puntero isa que apunta a su objeto de clase . El puntero isa es en realidad un puntero a una estructura de clase, que contiene información relacionada con la clase.

  • nombre de la clase;
  • Puntero a la clase principal;
  • Lista de variables miembro de la clase;
  • Lista de atributos de clase;
  • Lista de métodos de clase;
  • Listado de protocolos para la clase.

Un objeto de clase tiene uno y solo un objeto en la memoria, que incluye principalmente el puntero isa, el puntero de superclase, la información de atributos de la clase, la información del método del objeto de la clase, la información del protocolo de la clase y el miembro. información variable de la clase.

El puntero isa del objeto de clase es

// Class ISA;
    Class superclass;
    cache_t cache;              // 方法缓存 formerly cache pointer and vtable
    class_data_bits_t bits;    // 用于获取具体的类信息 class_rw_t * plus custom rr/alloc flagsflags
    ...

bitsAlmacena información como la lista de métodos de la clase y es class_data_bits_tuna estructura de tipo.

Ya sabemos dónde apunta el puntero isa, por lo que el problema se resume de la siguiente manera:

  • Los métodos de objeto, atributos, variables miembro e información de protocolo se almacenan en objetos de clase.
  • Los métodos de clase se almacenan en objetos de metaclase (los objetos de metaclase y las estructuras de memoria de clase son iguales pero tienen usos diferentes. Incluyen principalmente información de clase de métodos de clase y los demás están vacíos)
  • Los valores específicos de las variables miembro se almacenan en el objeto de instancia.

1.4 esMemberOfClass y esKindOfClass

Referencia de: iOS Caikeng isKindOfClass & isMemberOfClass

Aprenda de la implementación y comprenda la esencia
Diagrama de posición de clase:
Insertar descripción de la imagen aquí

Implementación de métodos de clase.

- (void)ClassMethod {
    
    
    BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
    BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
    BOOL re3 = [(id)[Person class] isKindOfClass:[Person class]];
    BOOL re4 = [(id)[Person class] isMemberOfClass:[Person class]];
     
    NSLog(@" re1 :%hhd re2 :%hhd re3 :%hhd re4 :%hhd",re1,re2,re3,re4);
    // 1 0 0 0 
}
+ (BOOL)isKindOfClass:(Class)cls {
    
    
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
    
    
        if (tcls == cls) return YES;
    }
    return NO;
}
//类方法
+ (BOOL)isMemberOfClass:(Class)cls {
    
    
    return self->ISA() == cls;
}

Implementación de métodos de instancia.

- (void)instanceMethod {
    
    
    BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
    BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
    BOOL re7 = [(id)[Person alloc] isKindOfClass:[Person class]];
    BOOL re8 = [(id)[Person alloc] isMemberOfClass:[Person class]];
    NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
    // 1 1 1 1
}
- (BOOL)isKindOfClass:(Class)cls {
    
    
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
    
    
        if (tcls == cls) return YES;
    }
    return NO;
}
- (BOOL)isMemberOfClass:(Class)cls {
    
    
    return [self class] == cls;
}

Tiempo de ejecución

1.4 Hablemos del mecanismo de mensajes de OC

OC es un lenguaje dinámico y el mecanismo de mensaje es una serie de procesos realizados cuando un objeto envía un mensaje.
El método de llamada de objeto OC no sabe dónde está el método específico durante la fase de compilación. Durante el proceso de ejecución, se envía un mensaje al objeto, se obtiene la dirección de la función a través del objeto y se llama a la función. Si no es así encontrado, se lanza una excepción.

Al enviar un mensaje a un objeto, objc_msgSendel método encuentra la clase del objeto basándose en el puntero isa del objeto y luego dispatchtablela busca en la tabla de envío de la clase () selector. Si no se puede encontrar el selector, objc_msgSend busca la clase principal a través del puntero a la clase principal y busca el selector en la tabla de despacho de la clase principal (dispatchtable), y así sucesivamente hasta la clase NSObject . Una vez que se encuentra el selector, el método objc_msgSend llama a la implementación según la dirección de memoria de la tabla de despacho.
De esta manera, el mensaje está vinculado a la implementación real del método durante la fase de ejecución.
jefeInsertar descripción de la imagen aquí

1.5 Proceso del mecanismo de reenvío de mensajes

El proceso del mecanismo de reenvío de mensajes de OC son principalmente los tres mecanismos de rescate.

  • Análisis de método dinámico: llamada resloveInstaanceMethodo resolveClassMethodmétodo, intente agregar implementación al método que no está implementado
  • Receptor de respaldo: llame forwaardingtargetForSelectoral método para intentar permitir que otros objetos de esta clase ejecuten esta función (reenvío rápido de mensajes)
  • Reenvío completo de mensajes: si no se realiza el reenvío rápido, se llaman a los métodos methodSignatureForSeletory forwardInvocationpara reenviar mensajes completos y métodos de reemplazo.

1.6 ¿Qué es el tiempo de ejecución?

tiempo de ejecución: el tiempo de ejecución es el núcleo del sistema iOS y su esencia es un conjunto de API de lenguaje C subyacentes. El tiempo de ejecución procesa parte del trabajo cuando el código se está ejecutando en lugar de cuando se compila para brindar soporte para las propiedades dinámicas del lenguaje Objective-C . Por lo tanto, muchas clases y miembros no se conocen cuando compilamos. En tiempo de ejecución, el código escrito se convierte en una ejecución de código determinista completa.
Apple: tiempo de ejecución de Objective-C

Objective-C es un lenguaje dinámico, lo que significa que no solo requiere un compilador, sino también un sistema de ejecución para crear clases y objetos dinámicamente, y realizar el paso y reenvío de mensajes. Todas las llamadas a métodos/generación de clases en Objective-C ocurren en tiempo de ejecución

interacción en tiempo de ejecución

Hay tres niveles de interacción en el sistema de ejecución en OC:

  1. A través del código fuente de OC: escribimos código OC y el sistema de tiempo de ejecución convierte automáticamente el código fuente que escribimos en código de tiempo de ejecución durante la fase de compilación detrás de escena y determina el método correspondiente para llamar durante el tiempo de ejecución.
  2. Métodos definidos por NSObject de la Fundación
  3. Llamando directamente a la función de tiempo de ejecución.
    Insertar descripción de la imagen aquí

Aplicaciones prácticas del tiempo de ejecución.

  1. Utilice AssociatedObject para agregar atributos a categorías
  2. Recorrer todas las variables miembro de la clase (modificar el color del texto del marcador de posición del campo de texto, convertir el diccionario en modelo, archivar y desarchivar automáticamente)
  3. Implementación del método de intercambio (método del sistema de intercambio)
  4. Método de adición dinámica: nunca había usado esto antes, pero después de comprender todo el proceso de reenvío de mensajes, podrá comprender por qué funciona.

Una pregunta de Objective-C: [autoclase] y [superclase]

  • ¿Qué genera el siguiente código?
    Por favor agregue la descripción de la imagen.

hijo y padre?
Por favor agregue la descripción de la imagen.

La diferencia entre yo y super:

  1. Self es un parámetro oculto de la clase y el primer parámetro de la implementación de cada método es self.
  2. super no es un parámetro oculto, en realidad es solo un "identificador del compilador" y es responsable de decirle al compilador que al llamar a un método, llame al método de la clase principal en lugar del método de esta clase.
  3. Al llamar a [superclase], el tiempo de ejecución llamará al método objc_msgSendSuper en lugar de objc_msgSend
OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )


/// Specifies the superclass of an instance. 
struct objc_super {
    
    
    /// Specifies an instance of a class.
    __unsafe_unretained id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained Class class;
#else
    __unsafe_unretained Class super_class;
#endif
    /* super_class is the first class to search */
};

En el método objc_msgSendSuper, el primer parámetro es una estructura objc_super. Hay dos variables en esta estructura, una es el receptor que recibe el mensaje y la otra es super_class, la clase principal de la clase actual.

La razón de mi malentendido es que pensé erróneamente [super class]que se llamaba[super_class class]。

El principio de funcionamiento de objc_msgSendSuper debería ser así:

  • Comience a buscar el selector en la lista de métodos de la clase principal superClass apuntada por la estructura objc_super. Después de encontrarlo, use objc->receiver para llamar al selector de la clase principal. Tenga en cuenta que la persona que llama final es objc->receiver, no super_class.
  • Entonces objc_msgSendSuper finalmente se transforma en lo siguiente
// 注意这里是从父类开始msgSend,而不是从本类开始,
objc_msgSend(objc_super->receiver, @selector(class))

/// Specifies an instance of a class.  这是类的一个实例
    __unsafe_unretained id receiver;   

- (Class)class {
    
    
    return object_getClass(self);
}

Porque se encuentra el IMP del método de clase en la clase principal NSObject y porque se pasa el parámetro objc_super->receiver = self . self es hijo y llama a la clase, por lo que el ID del parámetro IMP pasado al método de clase por objc_msgSend(self, @selector(class)) y objc_msgSendSuper(objc_super, @selector(class)) es la misma instancia de objeto, por lo que al final ambos La salida es la misma.

Gestión de la memoria

1.6 ¿Qué es la CRA?

El nombre completo de ARC Automatic Reference Countinges el mecanismo de gestión de memoria de Objective. Para decirlo directamente, se agrega al código retain/release. El código que originalmente debía agregarse manualmente para manejar el recuento de referencias de la administración de memoria puede ser completado automáticamente por el compilador.

Reglas básicas de ARC: Mientras un objeto sea apuntado por ningún fuerte, no será destruido. Si el objeto no es apuntado por ningún fuerte, será destruido. Un puntero de tipo débil también puede apuntar al objeto. pero no se retendrá el objeto.

ARC se utiliza para resolver el problema de objetos retainy releasecorrespondencia. Los problemas anteriores de pérdidas de memoria o liberaciones repetidas causadas por la gestión manual ya no existirán.

1.7 Implementación de ARC y MRC

MRC: la operación de adquirir memoria manualmente para un objeto mediante la retención y la liberación de la memoria se denomina MRC (conteo manual de referencias).

MRC

Cómo pensar en la gestión de la memoria

  1. Los objetos generados por usted mismo :alloc new copy mutableCopycrean y mantienen objetos por sí mismo.
  2. Los objetos que no genera usted mismo también los puede conservar usted mismo:retain
//取得的对象存在但不持有
id obj = [NSMutableArray array];
//持有该对象
[obj retain];
  1. Suelta el objeto que sostienes cuando no lo necesitesrelease
//自己持有对象
id obj = [[NSObject alloc] init];
//释放对象
//指向对象的指针仍然被保留在变量obj中,貌似可以访问,但对象一经释放绝对不可访问
[obj release];
  1. Los objetos que uno no posee no pueden ser liberados por uno mismo
 //非自己持有的对象无法释放,crash
 id obj = [NSMutableArray array];
 [obj release];

Principio de MRC_autorelease.
Autorelease es un método de administración de memoria en Objective-C. Utiliza un grupo de liberación automática para retrasar el tiempo de liberación del objeto. De hecho, solo retrasa la
llamada del objeto para liberar . Para cada liberación automática, el sistema simplemente pone El objeto en Después de ingresar al grupo de liberación automática actual y llamar al método de liberación automática, el contador del objeto permanece sin cambios . Cuando se libera el grupo, se llamará al método de liberación en todos los objetos del grupo.

Atención ⚠️

  • No todos los objetos colocados en el código del grupo de liberación automática se liberarán automáticamente y autoreleaselos métodos deben llamarse manualmente.
  • No llamar continuamente autorelease/ autoreleaseusar después de llamarrelease
  • Según MRC, cada llamada al método de retención de un objeto requiere un método de liberación correspondiente, y cada llamada al método alloc, copy o new requiere un método de liberación correspondiente o un método de liberación automática.

reglas del arco

La implementación de ARC se trata principalmente del aprendizaje de modificadores de propiedad.

__Fuerte

  • _strong修饰符是id类型和对象类型默认的所有权修饰符, No importa qué método se llame en ARC, la variable modificada por una referencia fuerte retendrá el objeto. Si ya está retenido, el recuento de referencias no aumentará.
  • El propietario del objeto de referencia fuerte y el ciclo de vida del objeto: la variable que contiene la referencia fuerte se descarta cuando excede su alcance, y el objeto referenciado se liberará cuando la referencia fuerte caduque. Podemos entender que el modificador de referencia fuerte es la transformación del titular.
  • __strongLa modificación de objetos puede provocar que fuertes referencias entre objetos provoquen referencias circulares.

El modificador __weak evita referencias circulares.

  • __weak Las referencias débiles no pueden contener instancias de objetos.

El modificador __unsafe_unretained es un modificador de propiedad inseguro. Las variables con el modificador __unsafe_unretained no pertenecen a los objetos de administración de memoria del compilador. Y propenso a colgar punteros.

  • Una variable de puntero modificada por débil se establece automáticamente en nulo después de que se destruye la dirección de memoria a la que apunta.
  • _Unsafe_Unretained no se establecerá en cero, lo que puede causar punteros colgantes y fallas .
  • Puntero colgante: la memoria a la que apunta el puntero se ha liberado, pero el puntero todavía existe o es un puntero salvaje. Por lo tanto, cuando se utilizan __unsafe_unretainedmodificadores, al asignar una variable con el modificador __strong, es necesario asegurarse de que el objeto asignado realmente exista, si no existe, el programa fallará.

__modificador de liberación automática

  • Llamada automática: el compilador comprobará si el nombre del método comienza con alloc/new/copy/mutableCopy y, de lo contrario, registrará automáticamente el objeto de valor de retorno en el grupo automático.
    Por favor agregue la descripción de la imagen.

implementación del CRA

__fuerte

código OC:

	id  __strong obj0 = [[NSObject alloc] init];
    NSLog(@"%@", obj0);

método interno

//初始化的两个方法如下:
objc_alloc_init
objc_storeStrong
//所有程序执行完之后:
objc_autoreleasePoolPop

función storeStrong

objc_storeStrong(id *location, id obj)
{
    
    
	//用prev保留被赋值对象原来所指向的对象
    id prev = *location;
    //如果所赋的值和被赋值对象所指的对象是同一个,就直接return不进行任何操作
    if (obj == prev) {
    
    
        return;
    }
    //如果所赋的值和被赋值对象所指的对象不是同一个
    //就先objc_retain使所赋的值对象的引用计数+1(因为赋值成功之后要持有)
    objc_retain(obj);
    //改变被赋值对象所指向的对象为新的对象
    *location = obj;
    //因为prev保留了被赋值对象原来所指向的对象,所以对prev进行objc_release使原来的旧对象引用计数-1,因为现在我们的被赋值对象已经不指向它了
    objc_release(prev);
}
EG:
obj = otherObj;
//会变成如下函数调用
objc_storeStrong(&obj, otherObj);

  1. Compruebe si la dirección del objeto de entrada y la dirección señalada por el puntero son las mismas.
  2. Objeto de retención, recuento de referencia + 1.
  3. El puntero apunta a obj.
  4. El recuento de referencia del objeto señalado originalmente es -1.
objc_retain
objc_retain(id obj)
{
    
    
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}

->método retener()

objc_object::retain()
{
    
    
    assert(!isTaggedPointer());

    if (fastpath(!ISA()->hasCustomRR())) {
    
    
        return rootRetain();
    }

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}

El proceso del método de retención está relacionado con si el puntero isa está optimizado. La optimización del puntero isa está determinada por su estructura (no puntero), y el recuento de referencias también implica (extra_c y has_sidetable_rc)

  • El isa optimizado puede almacenar información adicional.
    Por favor agregue la descripción de la imagen.
 uintptr_t nonpointer        : 1;//->表示使用优化的isa指针
 uintptr_t has_sidetable_rc  : 1;//1->在extra_rc存储引用计数将要溢出的时候,借助Sidetable(散列表)存储引用计数,has_sidetable_rc设置成1,未溢出的时候为0
 uintptr_t extra_rc          : 19;  //->存储引用计数

Insertar descripción de la imagen aquí

Para la implementación de retener

  1. La forma más sencilla de determinar si se trata de un puntero etiquetado es devolverlo directamente.
  2. Este no es el comienzo para determinar si se admite Nonpointer isa.
  3. No compatible: vaya a la tabla lateral para recuperar la información del recuento y realizar la operación de incremento. Sidetable_retain directo, esto se debe a que los recuentos se almacenan en la tabla lateral y la lógica de procesamiento es más simple que cuando se admite Nonpointer isa.
  4. Admite punteros optimizados:
  • Primero determine si debe admitir la arquitectura isa sin puntero, pero isa no tiene información adicional.
    Si no hay información adicional, significa lo mismo que no admitirla (determine si hay optimización). El recuento de referencias se almacena en la tabla lateral. , y se sigue el proceso de recuento de referencias de la tabla lateral + 1.
  • Luego determine si el objeto se está liberando y, si se está liberando, ejecute el proceso de desasignación.
  • Se almacena información adicional, incluidos los recuentos de referencia. Intentamos probar extra_rc++ más uno en isa
  • Si no hay una situación de desbordamiento o fuera de límites, cambiamos el valor de isa al valor después de extra_rc++.
    Si hay un desbordamiento, almacenamos la mitad del recuento en extra_rc y la otra mitad en la tabla lateral para establecer el bit de bandera en verdadero.

¿Cómo se optimiza el proceso de retención?

  • El núcleo radica en si isa admite el almacenamiento de información. La capacidad de isa para almacenar información nos evita leer la información de conteo en la tabla lateral y mejora la eficiencia (la liberación también se puede optimizar porque retener y liberar aparecen en pares).
struct SideTable {
    
    
    spinlock_t slock; // 保证原子操作的自旋锁
    RefcountMap refcnts; // 引用计数的 hash 表
    weak_table_t weak_table; // weak 引用全局 hash 表
};
objc_release
objc_release(id obj)
{
    
    
    if (obj->isTaggedPointerOrNil()) return;
    return obj->release();
}

Resumen del proceso de liberación:

  1. Aún así, determine si es un puntero etiquetado; si es así, devuelva falso; si no, desalloc.
  2. Determinar si hay optimización, sin operación de tabla hash, recuento de referencias + 1;
  3. Si el recuento de referencia es 0, si es 0, ejecute el proceso de asignación.
  4. Si isa está optimizado, el recuento de referencia almacenado en el bit isa del objeto se reduce en uno para determinar si se desborda. Si es así, si llega a -1, abandone newisa y cámbielo a antiguo, y saque la mitad del recuento de referencias en la tabla hash, y luego esta mitad del recuento de referencias se reduce en uno y se almacena en extra_rc de isa.
  5. Si el recuento de referencias de la tabla lateral es 0, el objeto se somete al proceso de desasignación.
  6. La diferencia con retener es que el recuento de referencias se reduce en uno.
    Insertar descripción de la imagen aquí
retenerContar

El almacenamiento del recuento de referencia de objetos se divide en dos situaciones:

  • Si el isa del objeto no es un puntero (optimización), el recuento de referencias se almacena tanto en el campo extra_rc como en la tabla lateral, lo que requiere su suma.
  • Si la isa del objeto es la isa original, los datos de recuento de referencia del objeto solo se guardan en la tabla lateral.

El proceso de retenciónCount

  1. Cuando el isa del objeto esté optimizado, primero obtenga el recuento de referencia en el campo de bits isa extra_rc, que será +1 de forma predeterminada (para evitar que imprima si no lo mantiene), luego obtenga el recuento de referencia en la tabla hash. tabla de recuento de referencias y sume las dos para obtener el recuento de referencia final del objeto
  2. Cuando el isa del objeto no se ha optimizado, el recuento de referencias en la tabla de recuento de referencias de la tabla hash se obtiene y devuelve directamente.
  3. Cuando se inicializa el objeto, el recuento de referencias por defecto es 1. El compilador determina en la parte inferior para evitar que el objeto se libere y lo incrementa en 1. Este 1 no aparecerá en la tabla lateral o extra_rc , porque el objeto en sí está almacenado en Tanto sidetabley y la suma son todos 0, y luego nuestras operaciones de suma posteriores son +1 o -1 para el recuento de referencias en la suma .extra_rcsidetableextra_rcretainreleasesidetableextra_rc

Referencia: iOS Parsing Runtime desde el código fuente (5): centrarse en objc_object (retener, liberar, retener recuento)

1.8 ¿Qué hace ARC en tiempo de compilación y de ejecución?

En tiempo de compilación, ARC puede simplificar las operaciones que se compensan entre sí retain. Cuando el mismo objeto se realiza múltiples operaciones de retención y liberación, ARC a veces puede eliminar estas dos operaciones en pares. ARC analizará la supervivencia del objeto release. , sin la necesidad de que usemos manualmente los métodos , y . El compilador también generará el método dealloc apropiado para usted.autoreleaseretainreleaseautorelease

autoreleaseARC puede detectar retaineste par de operaciones redundantes en tiempo de ejecución . Para optimizar el código, se ejecuta una función especial cuando se devuelve un objeto liberado automáticamente en un método.

1.9 Puntero etiquetado

Referencia: iOS - Gestión de memoria común (5): puntero etiquetado

Para ahorrar memoria y mejorar la eficiencia de ejecución, Apple ha introducido tecnología en programas de 64 bits Tagged Pointerpara optimizar NSNumber、NSDate、NSStringel almacenamiento de objetos pequeños como.

Fondo del puntero etiquetado

En una máquina de 64 bits , un puntero ocupa 8 bytes y un objeto que contiene un puntero isa también ocupa 8 bytes. Para un NSNumber que contiene un número entero, también debe haber 8 bytes para almacenar el número entero. Por tanto, un objeto de tipo NSNumber más un puntero ocupará al menos 24 bytes.

Apple diseñó Tagged Pointer para optimizar la memoria del objeto . En una máquina de 64 bits, coloca números enteros, tipos de caracteres o algunas cadenas de menor longitud directamente en el puntero y luego agrega los cuatro bits altos y bajos al puntero. Agregar un bit de etiqueta indica que el puntero actual es un puntero etiquetado e indica el tipo de datos actual. Esto facilita el almacenamiento y el acceso a los datos. Después de la introducción de Tagged Pointer, el uso de memoria se reducirá a más de la mitad y la velocidad de acceso aumentará 3 veces. Tagged Pointer no es un objeto y su proceso de creación y destrucción es mucho más rápido que el de los objetos. Tomando un NSNumber entero como ejemplo, sin usar Tagged Pointer, ocupa al menos 24 bytes. Después de usar Tagged Pointer, el número de bytes ocupados es 8 bytes. Se puede ver que la mejora en la conveniencia de la memoria aún es obvia.

Insertar descripción de la imagen aquí

Para almacenar y acceder a un objeto NSNumber, necesitamos asignarle memoria en el montón, y también mantener su recuento de referencias y administrar su vida útil. Estos añaden lógica adicional al programa, provocando una pérdida en la eficiencia operativa.
Para mejorar el uso de la memoria y los problemas de eficiencia mencionados anteriormente, Apple propuso el objeto Tagged Pointer. Dado que el tamaño de la memoria ocupada por los valores de variables como NSNumber y NSDate a menudo no requiere 8 bytes, tomando los números enteros como ejemplo, el número de enteros con signo que pueden representarse con 4 bytes puede alcanzar más de 2 mil millones. Entonces podemos dividir el puntero de un objeto en dos partes, una parte almacena directamente los datos y la otra parte se usa como una marca especial para indicar que se trata de un puntero especial que no apunta a ninguna dirección.

Insertar descripción de la imagen aquí

Por lo tanto, en términos simples, se puede entender que el contenido señalado por el puntero se coloca directamente en la dirección de memoria de la variable del puntero , porque en un entorno de 64 bits, el tamaño de la variable del puntero alcanza los 8 bits, que es suficiente para acomodar algún contenido más pequeño.
Por favor agregue la descripción de la imagen.

Tipos admitidos por puntero etiquetado

Tipos de datos comunes NSString, NSNumber, NSIndexPath, NSDate y UIColor compatibles con puntero etiquetado

OBJC_TAG_NSString          = 2, 
OBJC_TAG_NSNumber          = 3, 
OBJC_TAG_NSIndexPath       = 4, 
OBJC_TAG_NSDate            = 6,
OBJC_TAG_UIColor           = 17,

⚠️: Cuando la longitud de la cadena es inferior a 10 caracteres, el tipo de cadena es NSTaggedPointerString. Cuando supera los 10 caracteres, el tipo de cadena es __NSCFString.

La diferencia entre el puntero etiquetado y los objetos
En el proceso de generación del puntero etiquetado, las operaciones de bits en realidad se realizan en el puntero.

static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
    
    
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

El puntero etiquetado no es un objeto y no tiene un puntero isa. El proceso de asignación y destrucción de memoria también es diferente al de un objeto.

id objc_retain(id obj)
{
    
    
    if (_objc_isTaggedPointerOrNil(obj)) return obj;
    return obj->retain();
}

void objc_release(id obj)
{
    
    
    if (_objc_isTaggedPointerOrNil(obj)) return;
    return obj->release();
}

Durante la gestión del recuento de referencias, si se trata de un puntero etiquetado, la función volverá directamente.

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
    
    
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;
    //如果是Tagged Pointer,直接返回
    if (_objc_isTaggedPointerOrNil(referent)) return referent_id;

    //其他代码
    //...
}

Al modificar la tabla débil, si la referencia débil es un puntero etiquetado, el puntero etiquetado no se agregará a la tabla débil en este momento.

Características de TaggedPointer

   dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 1000; i++) {
    
    
        dispatch_async(queue, ^{
    
    
            self.name = [NSString stringWithFormat:@"abcdefghij"];
        });
    }

   dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 1000; i++) {
    
    
        dispatch_async(queue, ^{
    
    
            self.name = [NSString stringWithFormat:@"abcdefghi"];
        });
    }

Código 1 falla Código 2 normal

Imprima el tipo self.name de las dos piezas de código respectivamente. Resulta que self.name en la primera pieza de código es de tipo __NSCFString, mientras que en la segunda pieza de código es de tipo NSTaggedPointerString.

__NSCFString se almacena en el montón, es un objeto normal y necesita mantener un recuento de referencias. A self.name se le asigna un valor a través del método de establecimiento.

- (void)setName:(NSString *)name {
    
    
    if(_name != name) {
    
    
        [_name release];
        _name = [name retain]; // or [name copy]
    }
}

Si el método setter se ejecuta de forma asincrónica y concurrente, varios subprocesos pueden ejecutar [_name release] al mismo tiempo. Dos lanzamientos consecutivos provocarán una liberación excesiva del objeto, lo que provocará un bloqueo.
Solución:

  1. Utilice la palabra clave de atributo atómico.
  2. Cerrar

El NSString en el segundo fragmento de código es del tipo NSTaggedPointerString. En la función objc_release, se juzgará si el puntero es del tipo TaggedPointer. Si es así, el objeto no se liberará, evitando así el bloqueo causado por una liberación excesiva del objeto, porque no hay ninguna operación de liberación Realizar.

__attribute__((aligned(16), flatten, noinline))
void 
objc_release(id obj)
{
    
    
    if (!obj) return;
    if (obj->isTaggedPointer()) return;
    return obj->release();
}

Resumir

La introducción de Tagged Pointer también trae problemas, es decir, Tagged Pointer no es un objeto real, sino un pseudoobjeto. Todos los objetos tienen punteros isa, pero Tagged Pointer en realidad no lo tiene porque no es un objeto real.

Características del puntero etiquetado

  1. Tagged Pointer se utiliza especialmente para almacenar objetos pequeños, como NSNumber y NSDate.
  2. Puntero etiquetado El valor del puntero ya no es la dirección, sino el valor real.

1.10 alineación de la memoria de iOS

Referencia: principio de alineación de la memoria de iOS
Cómo obtener el tamaño de la memoria

  1. sizeof: Su función es devolver el número de bytes de memoria que ocupa un objeto o tipo.
  2. class_getInstaceSize: es una API proporcionada por el tiempo de ejecución, que se utiliza para obtener el tamaño de memoria ocupada por el objeto de instancia de la clase y devuelve el número específico de bytes.Su esencia es obtener el tamaño de memoria de las variables miembro en el objeto de instancia.
  3. malloc_size: obtiene el tamaño de memoria real asignado por el sistema

Por favor agregue la descripción de la imagen.
Resultado: 8 24 32

 Person *p1 = [[Person alloc] init];
    NSLog(@"p1对象类型占用的内存大小:%lu",sizeof(p1)); // 因为对象的本质是结构体指针,而指针占的是8个字节。
    NSLog(@"p1对象实际占用的内存大小:%lu",class_getInstanceSize([p1 class])); // 为什么对象实际占用24字节,不是20吗?isa(8字节)+NSString *name(8字节)+int age(4字节)? 确实字节大小共为20字节,但是依照內存对齐原则进行了字节补齐,所以补齐到了24字节(3个8字节放得下)。
    NSLog(@"p1对象实际分配的内存大小:%lu",malloc_size((__bridge const void *)(p1))); // malloc_size是系統分配的大小,以16字节对齐,大小20个字节要32字节(兩个16字节放得下)。
    

Razones para la alineación de la memoria

  1. Mejoras de rendimiento
  • La memoria no alineada requiere que el procesador realice dos accesos a la memoria; el acceso a la memoria alineada requiere solo un acceso. Lo más importante es mejorar el rendimiento del sistema de memoria.
  1. Corresponde a varias plataformas.
  • No todas las plataformas de hardware pueden acceder a datos en cualquier dirección; algunas plataformas de hardware solo pueden recuperar ciertos tipos de datos en ciertas direcciones; de lo contrario, se generará una excepción de hardware.

Reglas de alineación de la memoria estructural.

Cada compilador en una plataforma específica tiene su propio "factor de alineación" predeterminado (también llamado módulo de alineación). Los programadores pueden cambiar este coeficiente mediante el comando de precompilación #pragma pack(n), n=1,2,4,8,16, donde n es el "coeficiente de alineación" que desea especificar. En iOS, Xcode tiene por defecto #pragma pack(8), que es una alineación de 8 bytes.

Regla 1

  • Reglas de alineación de miembros de datos: el primer miembro de la estructura o unión se coloca en el desplazamiento 0. La posición inicial de cada almacenamiento de miembros de datos posterior debe ser un múltiplo entero del tamaño del miembro o del tamaño de los submiembros del miembro.

Regla 2

  • Estructura como miembro: si una estructura A tiene la estructura B como submiembro y B almacena elementos como char, int, double, etc., entonces B debe comenzar a almacenar desde double, que es un múltiplo entero de 8.

Regla tres

  • El tamaño total de la estructura, es decir, el resultado de sizeof, debe ser un múltiplo entero de su miembro interno más grande y cualquier deficiencia debe compensarse.
*/
- (void)Eg1 {
    
    
    struct StructA {
    
    
        double a;   // 8 (0-7)
        char b;     // 1 [8 1] (8)
        int c;      // 4 [9 4] 9 10 11 (12 13 14 15)
        short d;    // 2 [16 2] (16 17)
    } strA;
    struct StructB {
    
    
        double a;   //8 (0-7)
        int b;      //4 (8 9 10 11)
        char c;     //1 (12)
        short d;    //2 13 (14 15) - 16
    } strB;

    // 輸出
    NSLog(@"strA = %lu,strB = %lu", sizeof(strA), sizeof(strB));
    // strA = 24,strB = 16
}

Supongo que te gusta

Origin blog.csdn.net/weixin_61639290/article/details/131749238
Recomendado
Clasificación