【iOS】grupo de liberación automática

Hablemos de lo que hemos aprendido recientemente autoreleasepool: Es posible que hayamos escrito mucho código estúpido en nuestra vida diaria, que tiene muchos defectos, pero es posible que lo hayamos aprendido relativamente poco en ese momento y no lo entendiéramos bien, como el siguiente:

for (int i = 0; i < 1000000; i++) {
    
    
    NSNumber *num = [NSNumber numberWithInt:i];
}

一、@autoreleasepool{}

Cuando generalmente creamos mainel código de una función, encontraremos que hay algo en ella @autoreleasepool{}, después de usar clangla compilación: @autoreleasepool{...}se compila en {__AtAutoreleasePool __autoreleasepool; ... }.

¿Qué es esto __AtAutoreleasePoolexactamente ?

En realidad es una estructura. Se llama a objc_autoreleasePoolPush (void) al crear la variable de estructura __AtAutoreleasePool. Cuando se destruye, se llama a objc_autoreleasePoolPop (void *), es decir, su constructor y destructor, para que podamos ver que en realidad es. una variable del grupo de liberación automática encapsulada en C++. Agregará el contenido en {} en @autoreleasepool{…} al grupo de liberación automática para facilitar la administración de la memoria.

Pero mainparece que no sirve de nada en esta función, porque la memoria se liberará cuando finalice el programa, entonces, ¿por qué se agrega aquí @autoreleasepool{}?

Es técnicamente factible. No importa si eliminas @autoreleasepool{} en la función principal. Sin embargo, por razones de rigor, para que el objeto de liberación automática creado por UIApplicationMin tenga un grupo de liberación automática que se puede agregar y puede ser liberado cuando finaliza el grupo de liberación automática. El objeto no depende del reciclaje del sistema operativo, por lo que con @autoreleasepool{}, puede entenderse como el grupo de liberación automática más externo que activa el mecanismo de liberación.

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

struct __AtAutoreleasePool {
    
    
  __AtAutoreleasePool() {
    
    atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {
    
    objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

Entonces, lo que normalmente ARCescribimos en el entorno @autoreleasepool{}es en realidad un uso simple de la creación y liberación automática del grupo de lanzamiento.

Insertar descripción de la imagen aquí

Creará { un grupo de liberación automática en , destruirá el grupo de liberación automática en } y emitirá releaseuna notificación, permitiendo que las variables que contiene funcionen por sí solas release.

二、AutoreleasePoolPage

Desde arriba __AtAutoreleasePoolpodemos ver la objc_autoreleasePoolPushsuma de estos dos métodos objc_autoreleasePoolPop, pero ¿qué son exactamente?

void *objc_autoreleasePoolPush(void) {
    
    
    return AutoreleasePoolPage::push();
}
 
void objc_autoreleasePoolPop(void *ctxt) {
    
    
    AutoreleasePoolPage::pop(ctxt);
}

Podemos ver que aquí se ha introducido una nueva clase AutoreleasePoolPagey su código fuente relevante es el siguiente:

class AutoreleasePoolPage {
    
    
    magic_t const magic;//AutoreleasePoolPage 完整性校验
    id *next;//下一个存放autorelease对象的地址
    pthread_t const thread; //AutoreleasePoolPage 所在的线程
    AutoreleasePoolPage * const parent;//父节点
    AutoreleasePoolPage *child;//子节点
    uint32_t const depth;//深度,也可以理解为当前page在链表中的位置
    uint32_t hiwat;
}

Cada grupo de liberación automática se AutoreleasePoolPagecompone de una serie y AutoreleasePoolPageel tamaño de cada uno es de 4096 bytes (hexadecimal 0x1000).

#define I386_PGBYTES 4096
#define PAGE_SIZE I386_PGBYTES

Por lo tanto, podemos ver en el código fuente anterior que el grupo de lanzamiento automático es en realidad una lista doblemente vinculada compuesta por, y las AutoreleasePoolPagesumas en su estructura apuntan a su predecesor y sucesor respectivamente.childparent

Insertar descripción de la imagen aquí

La AutoreleasePoolPageestructura individual es la siguiente:
Insertar descripción de la imagen aquí

Se utilizan 56 bits para almacenar AutoreleasePoolPagevariables miembro y los 0x100816038 ~ 0x100817000 restantes se utilizan para almacenar objetos agregados al grupo de liberación automática.

  • La primera variable miembro de esta estructura es magic, que isatambién aprendimos en, isaque determina si el objeto no se ha inicializado, lo mismo ocurre aquí y se utiliza para comprobar si el nodo se ha inicializado.
  • begin()Y end()los métodos de instancia de estas dos clases nos ayudan a obtener rápidamente 0x100816038 ~ 0x100817000la dirección límite de este rango.
  • nextApunta a la siguiente dirección de memoria vacía. Si se agrega uno a la dirección señalada por next object, se moverá a la siguiente dirección de memoria vacía como se muestra en la figura siguiente, al igual que el puntero superior de la pila.
  • threadGuarda el hilo donde se encuentra la página actual.
  • depthLa profundidad representada pagees 0 por primera vez, y pageel tamaño de cada uno es 4096 bytes (0x1000 en hexadecimal), cada vez que se inicializa uno page, depthse incrementa en uno.
  • POOL_SENTINELEs el objeto centinela, es solo nilun alias de separación Autoreleasepool. POOL_BOUNDARYLa traducción literal es POOLla frontera de . Su función es separar pagelos objetos. Debido a que no todos los objetos almacenados entre y ocupan exactamente un bloque push, puede estar lleno o excedido, por lo que esto nos ayuda a separar los objetos entre cada bloque. En otras palabras, este objeto puede almacenar muchos bloques y se utiliza para separar los objetos de cada bloque.poppagePOOL_BOUNDARY@autoreleasepoolpage@autoreleasepoolPOOL_BOUNDARY@autoreleasepool
#define POOL_SENTINEL nil

Si agrega un objeto al que acaba de inicializar arriba page, se agregará en el punto señalado por next y next retrocederá una posición.
Insertar descripción de la imagen aquí

Y durante cada llamada de inicialización del grupo de liberación automática objc_autoreleasePoolPush, uno será empujado POOL_SENTINEL pusha la parte superior de la pila del grupo de liberación automática y POOL_SENTINELse devolverá el objeto centinela.

int main(int argc, const char * argv[]) {
    
    
    {
    
    
    	//这里的 atautoreleasepoolobj 就是一个 POOL_SENTINEL
        void * atautoreleasepoolobj = objc_autoreleasePoolPush();
        
        // do whatever you want
        
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    return 0;
}

El de arriba atautoreleasepoolobjes uno POOL_SENTINEL.
Cuando se llama al método objc_autoreleasePoolPop, se enviará un mensaje a los objetos en el grupo de liberación automática releasehasta el primero POOL_SENTINEL.

Insertar descripción de la imagen aquí

Esta es también autoreleasepoolla razón por la que los objetos se pueden liberar con precisión: en este autoreleasepool pushmomento, se devolverá POOL_SENTINELy se le dará la dirección de un objeto centinela ( ) pop, entonces popsabrá que cuando popse ejecute la liberación, se liberará al objeto centinela ( POOL_SENTINEL) y se detendrá. , y el contenido publicado es exactamente el objeto en el grupo de publicación automática.

1.método objc_autoreleasePoolPush:

void *objc_autoreleasePoolPush(void) {
    
    
    return AutoreleasePoolPage::push();
}

El método se llama aquí AutoreleasePoolPage::push():

static inline void *push() 
{
    
    
    id *dest;
    // POOL_BOUNDARY就是nil
    // 首先将一个哨兵对象插入到栈顶
    if (DebugPoolAllocation) {
    
    
        // 区别调试模式
        // 调试模式下将新建一个链表节点,并将一个哨兵对象添加到链表栈中
        // Each autorelease pool starts on a new pool page.
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
    
    
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}

autoreleaseFastEl método llamado hotPagese refiere al que se está utilizando actualmente AutoreleasePoolPage:

static inline id *autoreleaseFast(id obj)
{
    
    
   AutoreleasePoolPage *page = hotPage();
   if (page && !page->full()) {
    
    //有 hotPage 并且当前 page 不满,将object加入当前栈中
       return page->add(obj);
   } else if (page) {
    
    //有hotPage 但当前page已满,找未满页或创建新页,将object添加到新页中
       return autoreleaseFullPage(obj, page);
   } else {
    
    //无hotPage,创建hotPage,加入其中
       return autoreleaseNoPage(obj);
   }
}

1.1 Si hay hotPage y la página actual no está satisfecha, llame directamente a page->add(obj) para agregar el objeto al grupo de liberación automática.

// 这其实就是一个压栈操作,将对象加入AutoreleasePoolPage,然后移动栈顶指针
id *add(id obj) {
    
    
    id *ret = next;
    *next = obj;
    next++;
    return ret;
}

1.2 Hay una página caliente pero la página actual está llena. Busque la página que no está llena o cree una nueva página y agregue el objeto a la nueva página. autoreleaseFullPage (llamado cuando la página actual está llena):

static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
    
    
//一直遍历,直到找到一个未满的 AutoreleasePoolPage,如果找到最后还没找到,就新建一个 AutoreleasePoolPage
    do {
    
    
        if (page->child) 
        	page = page->child;
        else page = new AutoreleasePoolPage(page);
    } while (page->full());
	
	//将找到的,或者构建的page作为hotPage,然后将obj加入
    setHotPage(page);
    return page->add(obj);
}

1.3 Si no hay una hotPage, cree una hotPage y únase a ella:
en este momento, dado que no hay una hotPage en la memoria AutoreleasePoolPage, la lista doblemente vinculada del grupo de lanzamiento automático debe crearse desde cero y luego la tabla de páginas actual, como tabla de la primera página, no tiene parentpunteros. Y cuando lo creamos por primera vez pagehay que marcarlo POOL_SENTINELal principio, para que sepamos pagedónde termina.

static id *autoreleaseNoPage(id obj) {
    
    
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); // 创建AutoreleasePoolPage
    setHotPage(page); // 设置page为当前页
 
    if (obj != POOL_SENTINEL) {
    
     // 加POOL_SENTINEL哨兵
        page->add(POOL_SENTINEL);
    }
 
    return page->add(obj); // 将obj加入
}

método de liberación automática

Pensemos en ello ahora dest = autoreleaseFast(POOL_BOUNDARY): la operación pasa POOL_BOUNDARYvariables, no objetos, entonces, ¿cómo se almacena?

Al depurar y observar el ensamblaje, descubrimos que en realidad llamó a objc_retainAutoreleaseun método. Después de llamar capa por capa, descubrimos que llamó a autoreleaseun método:

static inline id autorelease(id obj)
{
    
    
    printf("static inline id autorelease%p\n", obj);
    assert(obj);
    assert(!obj->isTaggedPointer());
    id *dest __unused = autoreleaseFast(obj);
    assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
    return obj;
}

2.método objc_autoreleasePoolPop:

void objc_autoreleasePoolPop(void *ctxt) {
    
    
    AutoreleasePoolPage::pop(ctxt);
}

Por lo general, pasamos un objeto centinela en este método POOL_SENTINELpara facilitar la liberación, como se muestra en la siguiente figura para liberar el objeto:
Insertar descripción de la imagen aquí

El popmétodo de llamada es el siguiente:

static inline void pop(void *token) {
    
    
    AutoreleasePoolPage *page = pageForPointer(token);//使用 pageForPointer 获取当前 token 所在的 AutoreleasePoolPage
    id *stop = (id *)token;

    page->releaseUntil(stop);//调用 releaseUntil 方法释放栈中的对象,直到 stop 位置,stop就是传递的参数,一般为哨兵对象

	//调用 child 的 kill 方法,系统根据当前页的不同状态kill掉不同child的页面
	//releaseUntil把page里的对象进行了释放,但是page本身也会占据很多空间,所以要通过kill()来处理,释放空间
    if (page->child) {
    
    
        if (page->lessThanHalfFull()) {
    
     // 当前page小于一半满
            page->child->kill(); // 把当前页的孩子杀掉
        } else if (page->child->child) {
    
     // 否则,留下一个孩子,从孙子开始杀
            page->child->child->kill();
        }
    }
}

Apple asume que pagela mitad no está llena, lo que significa que el espacio restante es suficiente por el momento. Puedes eliminar pagetodo el exceso para liberar espacio. Si es más de la mitad, piensa que la página siguiente todavía está necesario. Tal vez los objetos agregados sean demasiado grandes. Cuanto más puedas usar, así que si pierdes un nieto , tener un hijo es suficiente por el momento.child pagekillpagekillpagepage

simbólico

  • okenes un indicador de poollaPOOL_BOUNDARY
  • tokenLa esencia es un puntero al objeto centinela, que almacena la dirección pushinsertada cada vez.POOL_BOUNDARY
  • Solo cuando presiones por primera vez se insertará pageun POOL_BOUNDARY[o pageestá lleno o no es hotPagenecesario usar uno nuevo page]. No pagesiempre comienza conPOOL_BOUNDARY

2.1 pageForPointer obtiene AutoreleasePoolPage:

pageForPointerEl método obtiene principalmente la primera dirección de la página donde se encuentra el puntero actual mediante la operación de direcciones de memoria:

static AutoreleasePoolPage *pageForPointer(const void *p) {
    
    
    return pageForPointer((uintptr_t)p);
}

static AutoreleasePoolPage *pageForPointer(uintptr_t p) {
    
    
    AutoreleasePoolPage *result;
    uintptr_t offset = p % SIZE;

    assert(offset >= sizeof(AutoreleasePoolPage));

    result = (AutoreleasePoolPage *)(p - offset);
    result->fastcheck(); // 检查当前的result是不是一个AutoreleasePoolPage

    return result;
}

Módulo el puntero y el tamaño de la página, que es 4096, para obtener el desplazamiento del puntero actual, porque todo AutoreleasePoolPageestá alineado en la memoria:

p = 0x100816048
p % SIZE = 0x48
result = 0x100816000

El último método llamado fastCheck()se utiliza para comprobar si el resultado actual es uno AutoreleasePoolPage.

Comprobando si un miembro de la estructura magic_t es 0xA1A1A1A1.

2.2 releaseUntil libera el objeto:

releaseUntilEl método se implementa de la siguiente manera:

void releaseUntil(id *stop) {
    
    
    while (this->next != stop) {
    
     // 不等于stop就继续pop
        AutoreleasePoolPage *page = hotPage(); // 获取当前页

        while (page->empty()) {
    
     // 当前页为空,就找其父页,并将其设置为当前页
            page = page->parent;
            setHotPage(page);
        }

        page->unprotect();
        id obj = *--page->next;
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); // 将内存内容标记为SCRIBBLE
        page->protect();

        if (obj != POOL_SENTINEL) {
    
     // 该对象不为标识POOL_SENTINEL,就释放对象
            objc_release(obj);
        }
    }

    setHotPage(this);
}

Su implementación sigue siendo muy sencilla: utiliza un whilebucle para liberar continuamente AutoreleasePoolPageel contenido hasta que se nextseñala stop.

Úselo memsetpara establecer el contenido de la memoria SCRIBBLEy luego use para objc_releaseliberar el objeto.

2.3 método kill():
en este punto, queda el único método sin análisis killy eliminará todas las páginas y subpáginas actuales:

void kill() {
    
    
    AutoreleasePoolPage *page = this; // 获取当前页
    while (page->child) page = page->child; // child存在就一直往下找,直到找到一个不存在的

    AutoreleasePoolPage *deathptr;
    do {
    
    
        deathptr = page;
        page = page->parent;
        if (page) {
    
    
            page->unprotect();
            page->child = nil; // 将其child指向置nil,防止出现悬垂指针
            page->protect();
        }
        delete deathptr; // 删除
    } while (deathptr != this); // 直到this处停止
}

3.método de liberación automática

Ya tenemos una mejor comprensión del ciclo de vida del grupo de liberación automática. El último tema que debemos comprender es la autoreleaseimplementación del método. Primero echemos un vistazo a la pila de llamadas del método:

- [NSObject autorelease]
└── id objc_object::rootAutorelease()
    └── id objc_object::rootAutorelease2()
        └── static id AutoreleasePoolPage::autorelease(id obj)
            └── static id AutoreleasePoolPage::autoreleaseFast(id obj)
                ├── id *add(id obj)
                ├── static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
                │   ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                │   └── id *add(id obj)
                └── static id *autoreleaseNoPage(id obj)
                    ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                    └── id *add(id obj)

En autoreleasela pila de llamadas de métodos, eventualmente se llamará al método mencionado anteriormente y se le autoreleaseFastagregará el objeto actual . La implementación de estos métodos en esta sección es muy sencilla: solo requiere algunas comprobaciones de parámetros y finalmente llama al método autoreleaseFast:AutoreleasePoolPage

inline id objc_object::rootAutorelease() {
    
    
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

__attribute__((noinline,used)) id objc_object::rootAutorelease2() {
    
    
    return AutoreleasePoolPage::autorelease((id)this);
}

static inline id autorelease(id obj) {
    
    
   id *dest __unused = autoreleaseFast(obj);
   return obj;
}

3. Respuestas a las preguntas iniciales

¿Entiendes el problema mencionado al principio? Cuando finaliza un ciclo en ejecución, es decir, antes de que autoreleasepoolse libere el objeto autorelease, autoreleasepoolla memoria sigue aumentando y la aplicación experimentará picos de memoria, se congelará e incluso el sistema puede obligarla a cerrarse y provocar un bloqueo.

Por lo tanto, al @autoreleasepoolasegurarnos de que los objetos generados en cada ciclo autoreleasese liberen a tiempo, podemos evitar los problemas anteriores:

for (int i = 0; i < 1000000; i++) {
    
    
   @autoreleasepool {
    
    
          NSNumber *num = [NSNumber numberWithInt:i];
          NSLog(@"%@", num);
   }
}

También existen @autoreleasepoolusos como la liberación retrasada, lo que permite que existan objetos más allá del alcance de la función.

4. Resumen

En general, autoreleasepooles una lista doblemente vinculada. Cada nodo en la lista vinculada es una pila. El puntero señalado se guarda en la pila autoreleasepooly se le agregan los objetos que el grupo debe liberar automáticamente, por lo que autoreleasepoolel recuento de referencia de todos los objetos que contenga serán +1. Una vez fuera autoreleasepool, no habrá ningún puntero que apunte al objeto, el recuento de referencia del objeto será -1 y, en ARC, xcodese agregará automáticamente para el código autoreleasepool.

  • El grupo de liberación automática se AutoreleasePoolPageimplementa en forma de una lista doblemente enlazada.
  • Cuando un objeto llama autoreleasea un método, el objeto se agrega AutoreleasePoolPagea la pila
  • Llamar a un método enviará un mensaje AutoreleasePoolPage::popal objeto en la pilarelease

Acerca de los objetos centinela (POOL_BOUNDARY) y los siguientes punteros:

Solo hay un puntero siguiente, que siempre apunta a la siguiente dirección donde se puede almacenar el autoreleasepool. Puede haber muchos objetos centinela. Cada autoreleasePool corresponde a un objeto centinela, lo que indica dónde comienza a almacenarse el objeto autoreleasePool.

next y child:
next apunta a la siguiente dirección que puede almacenar el objeto, y child es el parámetro de autoreleasePoolPage, que apunta a la página siguiente.

La relación entre autoreleasePoolPage y RunLoop:

Insertar descripción de la imagen aquí
RunLoop y AutoReleasePool se corresponden uno a uno a través de subprocesos
. Cuando el grupo de Autorelease no se agrega manualmente, el objeto Autorelease se libera antes de que el runloop actual entre en espera de suspensión
. Cuando un runloop está funcionando en un bucle sin parar, entonces el runloop definitivamente después BeforeWaiting (preparándose para entrar en suspensión): al ir a BeforeWaiting (preparándose para entrar en suspensión), se llamará a _objc_autoreleasePoolPop() y _objc_autoreleasePoolPush() para liberar el grupo antiguo y crear un nuevo grupo. Luego, estos dos métodos destruirán el objeto que se va a lanzado, por lo que no necesitamos preocuparnos en absoluto por los problemas de administración de memoria de Autorelease.

Momento en que RunLoop crea y libera el grupo de lanzamiento automático:

Al ingresar a RunLoop, cree un AutoReleasePool.
Cuando se prepare para dormir, libere el antiguo AutoReleasePool y cree un nuevo AutoReleasePool.
Cuando RunLoop salga, suelte AutoReleasePool.

¿Existe solo una lista doblemente vinculada para guardar autoreleasePoolPage? Es decir, ¿la página de grupo de liberación automática de todos los subprocesos se almacena en una lista vinculada o cada subproceso guarda su propia lista vinculada? ¿Y dónde se almacena el encabezado de la lista vinculada, que es la posición de entrada de la lista vinculada? ¿Quién lo controlará?

Un hilo tiene su propia lista enlazada de autoreleasePool independiente, o puede que no tenga una lista enlazada. La página activa de la lista vinculada se almacena en TLS. Debido a que la lista vinculada es bidireccional, el encabezado y la cola se pueden encontrar a través de la página activa y no es necesario almacenar el encabezado por separado.

Cuando necesita agregar usted mismo manualmente el grupo de liberación automática

  • Escriba programas que no se basen en marcos de interfaz de usuario, como herramientas de línea de comandos;
  • Cree una gran cantidad de objetos temporales en un bucle;
  • Utilice subprocesos secundarios creados por programas que no sean Cocoa;

Supongo que te gusta

Origin blog.csdn.net/m0_63852285/article/details/132168855
Recomendado
Clasificación