¿Cómo funciona el nuevo mecanismo de recuento automático de referencias?
¿Alguien puede explicarme brevemente cómo funciona ARC? Sé que es diferente de Garbage Collection, pero me preguntaba exactamente cómo funcionaba.
Además, si ARC hace lo que hace GC sin obstaculizar el rendimiento, ¿por qué Java usa GC? ¿Por qué no usa ARC también?
Cada nuevo desarrollador que llega a Objective-C tiene que aprender las reglas rígidas de cuándo retener, liberar y liberar automáticamente objetos. Estas reglas incluso especifican convenciones de nomenclatura que implican la retención del recuento de objetos devueltos por los métodos. La gestión de la memoria en Objective-C se convierte en algo natural una vez que se toman en serio estas reglas y se aplican de forma coherente, pero incluso los desarrolladores de Cocoa más experimentados cometen errores de vez en cuando.
Con Clang Static Analyzer, los desarrolladores de LLVM se dieron cuenta de que estas reglas eran lo suficientemente confiables como para poder crear una herramienta para señalar pérdidas de memoria y liberaciones excesivas dentro de las rutas que toma su código.
El conteo automático de referencias (ARC) es el siguiente paso lógico. Si el compilador puede reconocer dónde debería retener y liberar objetos, ¿por qué no hacer que inserte ese código por usted? Las tareas rígidas y repetitivas son en las que los compiladores y sus hermanos son excelentes. Los humanos olvidamos cosas y cometemos errores, pero las computadoras son mucho más consistentes.
Sin embargo, esto no te libera por completo de preocuparte por la gestión de la memoria en estas plataformas. Describo el problema principal a tener en cuenta (retener ciclos) en mi respuesta aquí , lo que puede requerir un poco de reflexión de su parte para marcar los puntos débiles. Sin embargo, eso es menor en comparación con lo que estás ganando en ARC.
En comparación con la administración de memoria manual y la recolección de basura, ARC le ofrece lo mejor de ambos mundos al eliminar la necesidad de escribir código de retención/liberación, pero sin tener los perfiles de memoria de detención y diente de sierra que se ven en un entorno de recolección de basura. Las únicas ventajas que tiene la recolección de basura sobre esto son su capacidad para lidiar con los ciclos de retención y el hecho de que las asignaciones de propiedades atómicas son económicas (como se analiza aquí ). Sé que estoy reemplazando todo mi código Mac GC existente con implementaciones ARC.
En cuanto a si esto podría extenderse a otros lenguajes, parece estar orientado al sistema de conteo de referencias en Objective-C. Puede ser difícil aplicar esto a Java u otros lenguajes, pero no sé lo suficiente sobre los detalles del compilador de bajo nivel para hacer una declaración definitiva al respecto. Dado que Apple es quien impulsa este esfuerzo en LLVM, Objective-C será lo primero a menos que otra parte comprometa importantes recursos propios para esto.
La presentación de esto sorprendió a los desarrolladores en la WWDC, por lo que la gente no sabía que se podía hacer algo como esto. Puede aparecer en otras plataformas con el tiempo, pero por ahora es exclusivo de LLVM y Objective-C.
ARC simplemente reproduce la retención/liberación antigua (MRC) y el compilador determina cuándo llamar a la retención/liberación. Tenderá a tener un mayor rendimiento, un menor uso máximo de memoria y un rendimiento más predecible que un sistema GC.
Por otro lado, algunos tipos de estructuras de datos no son posibles con ARC (o MRC), mientras que GC puede manejarlos.
Como ejemplo, si tiene una clase llamada nodo y el nodo tiene un NSArray de hijos y una referencia única a su padre que "simplemente funciona" con GC. Con ARC (y también con el conteo manual de referencias) tiene un problema. Se hará referencia a cualquier nodo determinado desde sus hijos y también desde su padre.
Como:
A -> [B1, B2, B3]
B1 -> A, B2 -> A, B3 -> A
Todo está bien mientras usa A (por ejemplo, a través de una variable local).
Cuando haya terminado con esto (y B1/B2/B3), un sistema GC eventualmente decidirá mirar todo lo que pueda encontrar comenzando desde la pila y los registros de la CPU. Nunca encontrará A, B1, B2, B3, por lo que los finalizará y reciclará la memoria en otros objetos.
Cuando usa ARC o MRC y termina con A, tendrá un recuento de referencia de 3 (B1, B2 y B3 hacen referencia a él), y B1/B2/B3 tendrá un recuento de referencia de 1 (el NSArray de A tiene una referencia a cada). Entonces todos esos objetos permanecen vivos aunque nada pueda usarlos.
La solución común es decidir que una de esas referencias debe ser débil (no contribuir al recuento de referencias). Eso funcionará para algunos patrones de uso, por ejemplo, si hace referencia a B1/B2/B3 solo a través de A. Sin embargo, en otros patrones falla. Por ejemplo, si a veces te aferras a B1 y esperas volver a subir a través del puntero principal y encontrar A. Con una referencia débil, si solo te aferras a B1, A puede (y normalmente lo hará) evaporarse y tomar B2 y B3. con eso.
A veces esto no es un problema, pero algunas formas muy útiles y naturales de trabajar con estructuras complejas de datos son muy difíciles de usar con ARC/MRC.
Entonces, ARC aborda el mismo tipo de problemas que aborda GC. Sin embargo, ARC funciona en un conjunto más limitado de patrones de uso que GC, por lo que si toma un lenguaje GC (como Java) y le inserta algo como ARC, algunos programas ya no funcionarán (o al menos generarán toneladas de memoria abandonada). , y puede causar serios problemas de intercambio o quedarse sin memoria o espacio de intercambio).
También se puede decir que ARC le da mayor prioridad al rendimiento (o tal vez a la previsibilidad), mientras que GC le da mayor prioridad a ser una solución genérica. Como resultado, GC tiene demandas de CPU/memoria menos predecibles y un rendimiento menor (normalmente) que ARC, pero puede manejar cualquier patrón de uso. ARC funcionará mucho mejor para muchos patrones de uso comunes, pero para algunos patrones de uso (¡válidos!) se caerá y morirá.