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 main
el código de una función, encontraremos que hay algo en ella @autoreleasepool{}
, después de usar clang
la compilación: @autoreleasepool{...}
se compila en {__AtAutoreleasePool __autoreleasepool; ... }
.
¿Qué es esto __AtAutoreleasePool
exactamente ?
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 main
parece 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 ARC
escribimos en el entorno @autoreleasepool{}
es en realidad un uso simple de la creación y liberación automática del grupo de lanzamiento.
Creará {
un grupo de liberación automática en , destruirá el grupo de liberación automática en } y emitirá release
una notificación, permitiendo que las variables que contiene funcionen por sí solas release
.
二、AutoreleasePoolPage
Desde arriba __AtAutoreleasePool
podemos ver la objc_autoreleasePoolPush
suma 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 AutoreleasePoolPage
y 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 AutoreleasePoolPage
compone de una serie y AutoreleasePoolPage
el 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 AutoreleasePoolPage
sumas en su estructura apuntan a su predecesor y sucesor respectivamente.child
parent
La AutoreleasePoolPage
estructura individual es la siguiente:
Se utilizan 56 bits para almacenar AutoreleasePoolPage
variables 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
, queisa
también aprendimos en,isa
que determina si el objeto no se ha inicializado, lo mismo ocurre aquí y se utiliza para comprobar si el nodo se ha inicializado. begin()
Yend()
los métodos de instancia de estas dos clases nos ayudan a obtener rápidamente0x100816038 ~ 0x100817000
la dirección límite de este rango.next
Apunta a la siguiente dirección de memoria vacía. Si se agrega uno a la dirección señalada por nextobject
, 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.thread
Guarda el hilo donde se encuentra la página actual.depth
La profundidad representadapage
es 0 por primera vez, ypage
el tamaño de cada uno es 4096 bytes (0x1000 en hexadecimal), cada vez que se inicializa unopage
,depth
se incrementa en uno.POOL_SENTINEL
Es el objeto centinela, es solonil
un alias de separaciónAutoreleasepool
.POOL_BOUNDARY
La traducción literal esPOOL
la frontera de . Su función es separarpage
los objetos. Debido a que no todos los objetos almacenados entre y ocupan exactamente un bloquepush
, 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.pop
page
POOL_BOUNDARY
@autoreleasepool
page
@autoreleasepool
POOL_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.
Y durante cada llamada de inicialización del grupo de liberación automática objc_autoreleasePoolPush
, uno será empujado POOL_SENTINEL push
a la parte superior de la pila del grupo de liberación automática y POOL_SENTINEL
se 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 atautoreleasepoolobj
es 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 release
hasta el primero POOL_SENTINEL
.
Esta es también autoreleasepool
la razón por la que los objetos se pueden liberar con precisión: en este autoreleasepool push
momento, se devolverá POOL_SENTINEL
y se le dará la dirección de un objeto centinela ( ) pop
, entonces pop
sabrá que cuando pop
se 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;
}
autoreleaseFast
El método llamado hotPage
se 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 parent
punteros. Y cuando lo creamos por primera vez page
hay que marcarlo POOL_SENTINEL
al principio, para que sepamos page
dó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_BOUNDARY
variables, no objetos, entonces, ¿cómo se almacena?
Al depurar y observar el ensamblaje, descubrimos que en realidad llamó a objc_retainAutorelease
un método. Después de llamar capa por capa, descubrimos que llamó a autorelease
un 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_SENTINEL
para facilitar la liberación, como se muestra en la siguiente figura para liberar el objeto:
El pop
mé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 page
la mitad no está llena, lo que significa que el espacio restante es suficiente por el momento. Puedes eliminar page
todo 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 page
kill
page
kill
page
page
simbólico
oken
es un indicador depool
laPOOL_BOUNDARY
token
La esencia es un puntero al objeto centinela, que almacena la direcciónpush
insertada cada vez.POOL_BOUNDARY
- Solo cuando presiones por primera vez se insertará
page
unPOOL_BOUNDARY
[opage
está lleno o no eshotPage
necesario usar uno nuevopage
]. Nopage
siempre comienza conPOOL_BOUNDARY
2.1 pageForPointer obtiene AutoreleasePoolPage:
pageForPointer
El 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 AutoreleasePoolPage
está 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:
releaseUntil
El 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 while
bucle para liberar continuamente AutoreleasePoolPage
el contenido hasta que se next
señala stop
.
Úselo memset
para establecer el contenido de la memoria SCRIBBLE
y luego use para objc_release
liberar el objeto.
2.3 método kill():
en este punto, queda el único método sin análisis kill
y 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 autorelease
implementació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 autorelease
la pila de llamadas de métodos, eventualmente se llamará al método mencionado anteriormente y se le autoreleaseFast
agregará 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 autoreleasepool
se libere el objeto autorelease
, autoreleasepool
la 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 @autoreleasepool
asegurarnos de que los objetos generados en cada ciclo autorelease
se 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 @autoreleasepool
usos como la liberación retrasada, lo que permite que existan objetos más allá del alcance de la función.
4. Resumen
En general, autoreleasepool
es una lista doblemente vinculada. Cada nodo en la lista vinculada es una pila. El puntero señalado se guarda en la pila autoreleasepool
y se le agregan los objetos que el grupo debe liberar automáticamente, por lo que autoreleasepool
el 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, xcode
se agregará automáticamente para el código autoreleasepool
.
- El grupo de liberación automática se
AutoreleasePoolPage
implementa en forma de una lista doblemente enlazada. - Cuando un objeto llama
autorelease
a un método, el objeto se agregaAutoreleasePoolPage
a la pila - Llamar a un método enviará un mensaje
AutoreleasePoolPage::pop
al 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:
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;