10

OK, entonces Apple nos trajo ARC, lo cual es genial. Después de refacturar mi Aplicación a ARC, casi todo funciona bien y ahora es mucho más fácil de desarrollar y mantener.Convirtiendo objetos de auto-liberación a ARC

Solo hay un problema que todavía no puedo resolver.

Mi programa de administración de trabajos muestra información detallada de propuestas, pedidos, etc. en sus propias ventanas. Así que tengo una clase especial donde WindowControllers consigue asignan y se iniciaron con initWithWindowNibName y luego la ventana se muestra con SHOWWINDOW:

DetailWindowController *proposalWindowController = [[DetailWindowController alloc] initWithWindowNibName:@"ThePorposalWindow"]; 
[proposalWindowController showWindow:nil]; 

Antes ARC la instancia de la WindowController hizo la liberación como se muestra en la documentation:

- (void)windowWillClose:(NSNotification *)notification 
{ 
    [self autorelease]; 
} 

Pero ahora con ARC esto ya no es posible y lo que empeora aún más, en mi clase especial donde se asigna e inicia el WindowController, ARC lanza el mismo windowController porque no hay ningún puntero al WindowController.

Mi idea era copiar el windowController en una matriz mutuable:

[proposalWindowArray addObject:proposalWindowController]; 
[[proposalWindowArray lastObject] showWindow:nil]; 

Y en el método windowControllers delegado windowWillClose puedo enviar una notificación a mi clase especial:

- (void)windowWillClose:(NSNotification *)notification 
{ 
    [[NSNotificationCenter defaultCenter] postNotificationName:@"ProposalWindowWillClose" object:[[self window] windowController] userInfo:nil]; 
} 

En mi clase especial Escucho la notificación y elimino el objeto de la matriz:

- (void) proposalWindowWasClosed: (NSNotification *) notification 
{ 
    [proposalWindowArray removeObjectIdenticalTo:[notification object]]; 
} 

Funciona, pero sigo sin creer que esta sea la forma correcta.

¿Alguien tiene el mismo problema o un consejo para mejorarlo?

+0

Su nuevo método es básicamente correcto. Con ARC, debe mantener referencias explícitas a los objetos para que el compilador pueda rastrear correctamente las llamadas retener/liberar. De hecho, creo que debes evitar los trucos como llamar a 'liberación' en los métodos de devolución de llamada en general, ya sea que uses ARC o no. –

+1

@RobKeniger: ¿Qué sugieres en su lugar? Imagina que estamos en el delegado de la aplicación que envía acciones recibidas para ver/controladores de ventana y generalmente hay muchos controladores diferentes involucrados. Crear una propiedad para cada uno parece innecesario. –

Respuesta

10

Probablemente usaría un enfoque de delegado en lugar de notificaciones. En general, es mejor tener un objeto externo que realice un seguimiento de las ventanas abiertas. Los objetos autoretenidos, como su sistema antiguo, rompen los puntos básicos de la propiedad del objeto y hacen que sea difícil encontrar cosas (como "darme una lista de ventanas abiertas").Los Non-Singletons que simplemente están "flotando" por ahí a menudo vuelven para morderte en tu arquitectura (he tenido que arreglar esto con frecuencia).

Dicho esto, a veces la autopropiedad es al menos conveniente, y en el peor de los casos, no es el fin del mundo. Por lo tanto, auto-propio. La única diferencia es que debe hacerlo de forma explícita en lugar de coincidir con una fuga y una sobreexplotación (que es lo que hacía su código anterior).

Cree una propiedad privada strong. Asignar self a él. Eso creará un bucle de retención que lo mantendrá cerca hasta que establezca la propiedad en nil.

+0

Entonces, ¿tu respuesta es mantener una matriz de subclases de controlador de ventana en tu clase que actúe como delegado y se llame en el método windowWillClose del controlador de ventana? –

+3

Dependiendo del sistema, a menudo solo tengo un singleton central de 'WindowManager' y le dejo ver 'NSWindowWillCloseNotification', pero a veces uso la delegación como dice. Depende de qué tan integrado debe ser. El WindowManager generalmente posee todos los WindowControllers en el sistema. Ese es solo el diseño que suelo encontrar cuando termino. –

+0

Rob: Después de investigar mucho más sobre este tema (especialmente trabajando en el curso "Desarrollo de aplicaciones para iPad y iPhone" de Paul Hegarty, encontrado en iTunes U, que recomiendo encarecidamente) acepto su última respuesta. Lo hice de esta manera (mirando el 'NSWindowWillCloseNotification') y parece ser la forma más elegante y razonable. Entonces, gracias a todos. – archibaldtuttle

0

Sin hacks, no hay una manera elegante de mantener un objeto retenido que no sea una referencia fuerte en algún otro objeto. Por ejemplo, puede mantener un NSMutableArray/NSMutableSet estático, agregar su controlador allí y eliminarlo en windowsWillClose:. Esto será más corto que publicar una notificación. Para que esto sea reutilizable, cree un singleton WindowControllerRegistry con una matriz, donde puede agregar controladores como este, y que escuchará automáticamente NSWindowWillCloseNotification y los eliminará de su matriz, liberando así la propiedad.

Como solución rápida, puede realizar retain/autorelease llamadas de non-ARC file:

my_retain(self); 
my_autorelease(self); 

// ArcDisabled.mm 
void my_retain(id obj) { [obj retain]; } 
void my_autorelease(id obj) { [obj autorelease]; } 
+0

Gracias hamstergene, eso probablemente funcione. Pero estoy buscando una solución un poco más elegante. Debe haber una solución sin usar retener y liberar. De lo contrario, no tuve que cambiar a ARC. – archibaldtuttle

+0

ARC hace retener/liberar una función de compilación. Llamarlos de esta manera es un comportamiento de compilador indefinido bajo ARC, y podría hacer cualquier cosa. Podría tener fugas, funcionar, bloquearse, a veces o siempre. Por especificaciones, podría venir y patear a tu perro. El compilador tiene optimizaciones que eliminan retenciones y lanzamientos innecesarios como lo considere oportuno. ARC a veces "falsifica" retener y liberar. Su enfoque puede desequilibrar el sistema según cómo se optimice el compilador. Más sobre el comportamiento indefinido: http://blog.regehr.org/archives/213 Más sobre las optimizaciones de ARC: http://bit.ly/friday-qa-2011-09-30 –

+0

@RobNapier El truco está sucio y no debería ser utilizado, pero ¿por qué crees que tiene un comportamiento indefinido? ARC no cancela el envío dinámico. – hamstergene

0

Creo que su enfoque alternativo debe ser correcta, pero no creo que necesita la segunda notificación. Usted debe ser capaz de hacer:

- (void)windowWillClose:(NSNotification *)notification 
{ 
    [proposalWindowArray removeObjectIdenticalTo:self]; 
} 

Suponiendo que el "proposalWindowArray" es una NSMutableArray estática.

+0

Desafortunadamente proposalWindowArray es una propiedad privada de mi clase especial detailWindowController que gestiona la asignación e inicialización de WindowControllers como proposalWindowController. Por lo tanto, no hay forma de obtener acceso a la propiedad privada de detailWindowController fuera de proposalWindowController. – archibaldtuttle

0

Tuve este mismo problema cuando cambié a ARC. Su solución funciona, pero la está haciendo demasiado complicada. En esencia, puede hacer lo que hacía antes haciendo que la ventana se libere cuando se cierra, pero de forma compatible con ARC.

La solución es simplemente crear una propiedad de su clase dentro de la misma clase. Para su ejemplo, en DetailWindowController, debe agregar la siguiente propiedad:

@property (strong) DetailWindowController  *theWindowController; 

A continuación, cuando se crea la ventana con el código anterior, añadir una línea así:

DetailWindowController *proposalWindowController = [[DetailWindowController alloc] initWithWindowNibName:@"ThePorposalWindow"]; 
[preferenceController setTheWindowController:proposalWindowController]; 
[proposalWindowController showWindow:nil]; 

Entonces, finalmente, tener ARC libera la ventana cuando está cerrada como lo hizo con la liberación automática pre-ARC, en la clase DetailWindowController, simplemente haga:

- (void)windowWillClose:(NSNotification *)notification 
{ 
    // Let ARC tear this down and clean it up 
    [self setTheWindowController:nil]; 
}