2010-10-20 13 views
5

estoy tratando de escribir esto forma más concisa posible, pero no es fácil de describir - así que gracias por la lectura =)Bloques del objetivo C: ¿Hay alguna forma de evitar que se retenga el "yo"?

Soy el principal desarrollador del Marco iPhone Open Source Sparrow. Sparrow se basa en la biblioteca Flash AS3 y, por lo tanto, tiene un sistema de eventos como AS3. Actualmente, ese sistema funciona al especificar selectores, pero me encantaría expandir ese sistema al permitir el uso de bloques para los oyentes de eventos. Sin embargo, estoy tropezando con problemas de gestión de memoria.

Le mostraré un caso de uso típico de eventos, tal como se manejan ahora.

// init-method of a display object, inheriting from 
// the base event dispatcher class 
- (id)init 
{ 
    if (self = [super init]) 
    { 
     // the method 'addEventListener...' is defined in the base class 
     [self addEventListener:@selector(onAddedToStage:) 
         atObject:self 
         forType:SP_EVENT_TYPE_ADDED_TO_STAGE]; 
    } 
    return self; 
} 

// the corresponding event listener 
- (void)onAddedToStage:(SPEvent *)event 
{ 
    [self startAnimations]; // call some method of self 
} 

Eso es bastante directo: cuando un objeto se agrega a la lista de visualización, recibe un evento. Actualmente, la clase base registra los oyentes de eventos en una matriz de NSInvocation-objects. La Invocación NS se crea de forma que no contiene y mantiene su objetivo y sus argumentos. (El usuario puede hacerlo, pero en el 99% de los casos, no es necesario).

El hecho de que no se conserven estos objetos fue una elección consciente: de lo contrario, el código anterior provocaría una pérdida de memoria, ¡incluso si el usuario eliminara el detector de eventos en el método dealloc! He aquí por qué:

- (id)init 
{ 
    if (self = [super init]) 
    { 
     // [self addEventListener: ...] would somehow cause: 
     [self retain]; // (A) 
    } 
    return self; 
} 

// the corresponding event listener 
- (void)dealloc 
{ 
    // [self removeEventListener...] would cause: 
    [self release]; // (B) 
    [super dealloc]; 
} 

A primera vista, esto parece muy bien: la retienen en el método init-se empareja por una liberación en el método dealloc. Sin embargo, eso no funciona, ya que nunca se llamará al método dealloc, ¡porque el recuento de retenciones nunca llega a cero!

Como dije, el método 'addEventListener ...'- hace, por exactamente este motivo, no retener nada en su versión predeterminada. Debido a la forma en que funcionan los eventos (casi siempre son enviados por objetos "propios" o "secundarios", que se retienen de todos modos), eso no es un problema.

Sin embargo, y ahora llegamos a la parte central de la pregunta: No puedo hacer eso con bloques. Mira el bloque-variante de la gestión de eventos, como me gustaría que tenga:

- (id)init 
{ 
    if (self = [super init]) 
    { 
     [self addEventListenerForType:ADDED_TO_STAGE block:^(SPEvent *event) 
     { 
      [self startAnimations]; 
     }]; 
    } 
    return self; 
} 

Eso se ve muy bien y sería muy fácil de usar. Sin embargo: cuando el usuario llama a un método en 'self' o usa una variable miembro dentro del bloque, que será, bueno, casi siempre, el bloque se retendrá automáticamente 'self', y el objeto nunca será desasignado. .

Ahora, sé que cualquier usuario podría rectificar esta haciendo una referencia __block a uno mismo, de esta manera:

__block id blockSelf = self; 
[self addEventListenerForType:ADDED_TO_STAGE block:^(SPEvent *event) 
{ 
    [blockSelf startAnimations]; 
}]; 

Pero, honestamente, estoy seguro de que casi todos los usuarios no sabrían hacerlo u olvida hacerlo Una API no solo debe ser fácil de usar, sino también difícil de usar erróneamente, y esto claramente infringe ese principio. Los usuarios de la API definitivamente lo usarían indebidamente.

Lo que me molesta es que yo que 'yo' no tiene que ser retenido, funciona en mi implementación actual sin retenerlo. Así que I quiero decirle al bloque que no necesita retenerme, la biblioteca debe decirle al bloque eso, para que el usuario no tenga que pensar en ello.

En mi investigación, no he encontrado una manera de hacerlo.Y no puedo pensar en una forma de cambiar mi arquitectura para que se ajuste a esa limitación de bloques.

¿Alguien tiene una idea de lo que podría hacer al respecto?
Incluso si no lo has hecho, gracias por leer esto hasta ahora, sé que fue una pregunta prolija ;-)

Respuesta

5

Discutí este tema con el soporte de Apple, y me dijeron lo que esperaba: actualmente, no hay forma de que me dejen decir al bloque que no debe retenerse. Sin embargo, ofrecieron dos formas de evitar el problema.

El primero sería pasar auto como argumento al bloque en lugar de una variable de bloque. La API se convertiría en:

[self addEventListenerForType:ADDED_TO_STAGE 
         block:^(id selfReference, SPEvent *event) 

La API sería por lo tanto responsable de pasar el auto (no retenido). A los desarrolladores aún les tendrían que decir que deben usar esta referencia en lugar de ella, pero al menos sería fácil de usar.

La otra solución que Apple ha utilizado en este tipo de situación es proporcionar un método separado de release/dealloc para cerrar el oyente. Un buen ejemplo de esto es el método "invalidante" de NSTimer. Fue creado debido a un ciclo de memoria entre NSRunLoop y NSTimer (por diseño).

+1

Esta es una buena manera de hacerlo. Pero, para el registro, aliasing 'self' con una variable' __block' evitará la retención también. –

1

Creo que el diseño es fundamentalmente defectuoso, bloques o no bloques. Está agregando objetos como detectores de eventos antes de que estén completamente inicializados. Del mismo modo, el objeto aún podría recibir eventos mientras se destraba. La adición y eliminación de detectores de eventos debe estar desacoplada de la OMI de asignación/desasignación.

En cuanto a su problema inmediato, es posible que no tenga que preocuparse por ello. Está teniendo un problema porque está implícitamente agregando una referencia a un objeto consigo mismo (indirectamente) al hacer referencia en el bloque. Tenga en cuenta que es improbable que cualquier otra persona que suministre un bloque haga referencia al objeto que genera el evento o sus variables de instancia porque no estarán dentro del alcance donde se define el bloque. Si lo son (en una subclase, por ejemplo), no hay nada que pueda hacer excepto documentar el problema.

Una forma de reducir la probabilidad de un problema es suministrar el objeto que genera el evento como una propiedad en su parámetro SPEvent y usarlo en el bloque en lugar de hacer referencia a uno mismo. Deberá hacer eso de todos modos porque no es necesario crear un bloque en un contexto donde el objeto que genera el evento esté dentro del alcance.

+0

Muchas gracias por su respuesta, Jeremy! En cuanto al diseño: IMO, no hay nada de malo en crear un detector de eventos en el método init, siempre que escuchen eventos de objetos 'propios' o secundarios. Y ese es el caso aquí. Pero de todos modos: el problema es que se usará de la manera que mencionas arriba: en el método init de una subclase. Por lo tanto, los usuarios tendrán acceso a sí mismos y causarán el problema. El objeto SPEvent ya tiene ese parámetro. Me temo que los usuarios escribirán el código como el anterior, y si eso no se puede resolver desde la biblioteca, preferiría dejar los bloques en absoluto. – PrimaryFeather

+0

Debe documentar la fuga potencial a continuación. No hay nada que pueda hacer para evitar que hagan referencia a las variables propias o de instancia de sí mismo, y la retención de sí mismo por parte del bloque está documentada por Apple. – JeremyP

+0

Qué lástima :-(De todos modos, gracias de nuevo por su tiempo Honestamente, en ese caso, creo que uno no puede recomendar el uso de bloques que deben mantenerse durante un tiempo en una plataforma sin recolección de basura. se usan en NSArray, etc., están bien, pero todo lo demás es demasiado peligroso en mi humilde opinión. Es demasiado fácil pasarlo por alto. - No marcaré la pregunta como respondida de inmediato, tal vez otro usuario tenga otra idea; Espero que estés bien con eso! – PrimaryFeather

Cuestiones relacionadas