capturarse fuertemente en este bloque es probable que conduzca a un ciclo de retención

207

¿Cómo puedo evitar esta advertencia en xcode? Aquí está el fragmento de código:

[player(AVPlayer object) addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
queue:nil usingBlock:^(CMTime time) {
    current+=1;

    if(current==60)
    {
        min+=(current/60);
        current = 0;
    }

    [timerDisp(UILabel) setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];///warning occurs in this line
}];
usuario1845209
fuente
¿Hay timerDispuna propiedad en la clase?
Tim
Sí, @property (no atómico, fuerte) UILabel * timerDisp;
user1845209
2
¿Qué es esto: player(AVPlayer object)y timerDisp(UILabel)?
Carl Veazey
Reproductor AVPlayer *; UILabel * timerDisp;
user1845209
55
La verdadera pregunta es cómo silenciar esta advertencia sin una referencia débil innecesaria en uno mismo, cuando sabe que la referencia circular se romperá (por ejemplo, si siempre borra la referencia cuando finaliza una solicitud de red).
Glenn Maynard

Respuestas:

514

La captura de selfaquí viene con su acceso implícito a la propiedad de self.timerDisp: no puede hacer referencia a las selfpropiedades selfdesde dentro de un bloque que será fuertemente retenido por self.

Puede evitar esto creando una referencia débil selfantes de acceder timerDispdentro de su bloque:

__weak typeof(self) weakSelf = self;
[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
                                     queue:nil
                                usingBlock:^(CMTime time) {
                                                current+=1;

                                                if(current==60)
                                                {
                                                    min+=(current/60);
                                                    current = 0;
                                                }

                                                 [weakSelf.timerDisp setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];
                                            }];
Tim
fuente
13
Intenta usar en su __unsafe_unretainedlugar.
Tim
63
Resuelto. use esto en su lugar: __unsafe_unretained typeof (self) weakSelf = self; gracias por la ayuda @Tim
user1845209
1
Buena respuesta, pero me molesto que diga: "no puede referirse a uno mismo ni a las propiedades en sí mismo desde un bloque que será fuertemente retenido por sí mismo". Esto no es estrictamente cierto. Por favor vea mi respuesta a continuación. Mejor decir, " debes tener mucho cuidado si te refieres a ti mismo ..."
Chris Suter
8
No veo un ciclo de retención en el código del OP. El bloque no está fuertemente selfretenido por la cola de despacho principal. ¿Me equivoco?
erikprice
3
@erikprice: no te equivocas. Interpreté la pregunta principalmente sobre el error que presenta Xcode ("¿Cómo puedo evitar esta advertencia en xcode"), en lugar de sobre la presencia real de un ciclo de retención. Estás en lo correcto al decir que no se evidencia ningún ciclo de retención solo por el fragmento de OP proporcionado.
Tim
52
__weak MyClass *self_ = self; // that's enough
self.loadingDidFinishHandler = ^(NSArray *receivedItems, NSError *error){
    if (!error) {
       [self_ showAlertWithError:error];
    } else {
       self_.items = [NSArray arrayWithArray:receivedItems];
       [self_.tableView reloadData];
    }
};

Y una cosa muy importante para recordar: no use variables de instancia directamente en bloque, úselo como propiedades de un objeto débil, muestra:

self.loadingDidFinishHandler = ^(NSArray *receivedItems, NSError *error){
        if (!error) {
           [self_ showAlertWithError:error];
        } else {
           self_.items = [NSArray arrayWithArray:receivedItems];
           [_tableView reloadData]; // BAD! IT ALSO WILL BRING YOU TO RETAIN LOOP
        }
 };

y no te olvides de hacer:

- (void)dealloc {
    self.loadingCompletionHandler = NULL;
}

puede aparecer otro problema si pasa una copia débil o no retenida por ningún objeto:

MyViewController *vcToGo = [[MyViewCOntroller alloc] init];
__weak MyViewController *vcToGo_ = vcToGo;
self.loadingCompletion = ^{
    [vcToGo_ doSomePrecessing];
};

si vcToGose desasignará y luego se disparará este bloque, creo que se bloqueará con un selector no reconocido en una papelera que vcToGo_ahora contiene una variable. Intenta controlarlo.

iiFreeman
fuente
3
Esta sería una respuesta más fuerte si también la explicas.
Eric J.
43

Mejor versión

__strong typeof(self) strongSelf = weakSelf;

Cree una referencia fuerte a esa versión débil como la primera línea de su bloque. Si self todavía existe cuando el bloque comienza a ejecutarse y no ha vuelto a cero, esta línea asegura que persista durante toda la vida de ejecución del bloque.

Así que todo sería así:

// Establish the weak self reference
__weak typeof(self) weakSelf = self;

[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
                                 queue:nil
                            usingBlock:^(CMTime time) {

    // Establish the strong self reference
    __strong typeof(self) strongSelf = weakSelf;

    if (strongSelf) {
        [strongSelf.timerDisp setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];
    } else {
        // self doesn't exist
    }
}];

He leído este artículo muchas veces. Este es un excelente artículo de Erica Sadun sobre cómo evitar problemas al usar bloques y NSNotificationCenter


Actualización rápida:

Por ejemplo, en swift, un método simple con bloque de éxito sería:

func doSomeThingWithSuccessBlock(success: () -> ()) {
    success()
}

Cuando llamamos a este método y necesitamos usarlo selfen el bloque de éxito. Usaremos las funciones [weak self]y guard let.

    doSomeThingWithSuccessBlock { [weak self] () -> () in
        guard let strongSelf = self else { return }
        strongSelf.gridCollectionView.reloadData()
    }

Este llamado baile fuerte-débil es utilizado por un popular proyecto de código abierto Alamofire.

Para obtener más información, consulte la guía de estilo rápida

Warif Akhand Rishi
fuente
¿Qué pasa si lo hiciste typeof(self) strongSelf = self;fuera del bloque (en lugar de __weak) luego en el bloque que se dice strongSelf = nil;después del uso? No veo cómo su ejemplo asegura que weakSelf no sea nulo para cuando se ejecute el bloque.
Matt
Para evitar posibles ciclos de retención, establecemos una autorreferencia débil fuera de cualquier bloque que use self en su código. En este sentido, debe asegurarse de que el bloque se ejecute. Otro bloque de su código ahora es responsable de liberar su memoria previamente retenida.
Warif Akhand Rishi
@Matt, el propósito de este ejemplo no es retener al débil. El objetivo es, si el débil Self no es nulo, hacer una referencia fuerte dentro del bloque. Entonces, una vez que el bloque comienza a ejecutarse con self, self no se vuelve nulo dentro del bloque.
Warif Akhand Rishi
15

En otra respuesta, Tim dijo:

no puede referirse a self o propiedades en self desde un bloque que será fuertemente retenido por self.

Esto no es del todo cierto. Está bien que hagas esto siempre que rompas el ciclo en algún momento. Por ejemplo, supongamos que tiene un temporizador que se dispara que tiene un bloqueo que se retiene a sí mismo y también mantiene una fuerte referencia al temporizador en sí mismo. Esto está perfectamente bien si siempre sabes que destruirás el temporizador en algún momento y romperás el ciclo.

En mi caso, justo ahora, recibí esta advertencia para el código que hizo:

[x setY:^{ [x doSomething]; }];

Ahora sé que clang solo producirá esta advertencia si detecta que el método comienza con "set" (y otro caso especial que no mencionaré aquí). Para mí, sé que no hay peligro de que haya un bucle de retención, así que cambié el nombre del método a "useY:" Por supuesto, eso podría no ser apropiado en todos los casos y generalmente querrá usar una referencia débil, pero Pensé que valía la pena señalar mi solución en caso de que ayude a otros.

Chris Suter
fuente
4

Muchas veces, esto no es realmente un ciclo de retención .

Si sabes que no es así, no necesitas traer débiles infructuosos al mundo.

Apple incluso nos impone estas advertencias con la API UIPageViewController, que incluye un método establecido (que activa estas advertencias, como se mencionó en otra parte, pensando que está estableciendo un valor en un ivar que es un bloque) y un bloque de controlador de finalización (en el que sin duda te referirás a ti mismo).

Aquí hay algunas directivas del compilador para eliminar la advertencia de esa línea de código:

#pragma GCC diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
    [self.pageViewController setViewControllers:@[newViewController] direction:navigationDirection animated:YES completion:^(BOOL finished) {
        // this warning is caused because "setViewControllers" starts with "set…", it's not a problem
        [self doTheThingsIGottaDo:finished touchThePuppetHead:YES];
    }];
#pragma GCC diagnostic pop
bshirley
fuente
1

Agregar dos centavos para mejorar la precisión y el estilo. En la mayoría de los casos, solo usará uno o un par de miembros selfen este bloque, lo más probable es que solo actualice un control deslizante. El casting selfes exagerado. En cambio, es mejor ser explícito y lanzar solo los objetos que realmente necesita dentro del bloque. Por ejemplo, si se trata de una instancia de UISlider*, digamos _timeSlider, simplemente haga lo siguiente antes de la declaración de bloque:

UISlider* __weak slider = _timeSlider;

Luego, solo use sliderdentro del bloque. Técnicamente, esto es más preciso, ya que reduce el ciclo de retención potencial solo al objeto que necesita, no a todos los objetos que contiene self.

Ejemplo completo:

UISlider* __weak slider = _timeSlider;
[_embeddedPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1)
     queue:nil
     usingBlock:^(CMTime time){
        slider.value = time.value/time.timescale;
     }
];

Además, lo más probable es que el objeto que se está lanzando a un puntero débil ya sea un puntero débil en el interior, lo selfque minimiza o elimina por completo la probabilidad de un ciclo de retención. En el ejemplo anterior, en _timeSliderrealidad es una propiedad almacenada como una referencia débil, por ejemplo:

@property (nonatomic, weak) IBOutlet UISlider* timeSlider;

En términos de estilo de codificación, como en C y C ++, las declaraciones de variables se leen mejor de derecha a izquierda. Declarando SomeType* __weak variableen este orden lee de forma más natural de derecha a izquierda como: variable is a weak pointer to SomeType.

Luis Artola
fuente
1

Me encontré con esta advertencia recientemente y quería entenderla un poco mejor. Después de un poco de prueba y error, descubrí que se origina por tener un método que comienza con "agregar" o "guardar". El objetivo C trata los nombres de métodos que comienzan con "new", "alloc", etc. como devolviendo un objeto retenido pero no menciona (que puedo encontrar) nada sobre "agregar" o "guardar". Sin embargo, si uso un nombre de método de esta manera:

[self addItemWithCompletionBlock:^(NSError *error) {
            [self done]; }];

Veré la advertencia en la línea [hecho a sí mismo]. Sin embargo, esto no:

[self itemWithCompletionBlock:^(NSError *error) {
    [self done]; }];

Seguiré adelante y usaré la forma "__weak __typeof (self) weakSelf = self" para hacer referencia a mi objeto, pero realmente no me gusta tener que hacerlo, ya que confundirá a un futuro yo y / u otro desarrollador. Por supuesto, tampoco podría usar "agregar" (o "guardar"), pero eso es peor ya que elimina el significado del método.

Ray M.
fuente