¿Siempre pasa la referencia débil de uno mismo al bloque en ARC?

252

Estoy un poco confundido sobre el uso de bloques en Objective-C. Actualmente uso ARC y tengo bastantes bloques en mi aplicación, actualmente siempre me refiero en selflugar de su referencia débil. ¿Puede ser esa la causa de que estos bloques selfretengan y eviten que sean desalojados? La pregunta es, ¿debería usar siempre una weakreferencia de selfen un bloque?

-(void)handleNewerData:(NSArray *)arr
{
    ProcessOperation *operation =
    [[ProcessOperation alloc] initWithDataToProcess:arr
                                         completion:^(NSMutableArray *rows) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self updateFeed:arr rows:rows];
        });
    }];
    [dataProcessQueue addOperation:operation];
}

ProcessOperation.h

@interface ProcessOperation : NSOperation
{
    NSMutableArray *dataArr;
    NSMutableArray *rowHeightsArr;
    void (^callback)(NSMutableArray *rows);
}

ProcessOperation.m

-(id)initWithDataToProcess:(NSArray *)data completion:(void (^)(NSMutableArray *rows))cb{

    if(self =[super init]){
        dataArr = [NSMutableArray arrayWithArray:data];
        rowHeightsArr = [NSMutableArray new];
        callback = cb;
    }
    return self;
}

- (void)main {
    @autoreleasepool {
        ...
        callback(rowHeightsArr);
    }
}
el crítico
fuente
Si desea un discurso en profundidad sobre este tema, lea dhoerl.wordpress.com/2013/04/23/…
David H

Respuestas:

721

Ayuda a no centrarse en parte strongo weaken la discusión. En cambio, concéntrate en la parte del ciclo .

Un ciclo de retención es un ciclo que ocurre cuando el Objeto A retiene el Objeto B, y el Objeto B retiene el Objeto A. En esa situación, si cualquiera de los objetos se libera:

  • El objeto A no se desasignará porque el objeto B tiene una referencia a él.
  • Pero el Objeto B nunca será desasignado mientras el Objeto A tenga una referencia a él.
  • Pero el Objeto A nunca será desasignado porque el Objeto B tiene una referencia a él.
  • indefinidamente

Por lo tanto, esos dos objetos permanecerán en la memoria durante toda la vida del programa, aunque deberían, si todo funcionara correctamente, ser desasignado.

Entonces, lo que nos preocupa es retener ciclos , y no hay nada sobre los bloques en sí mismos que crean estos ciclos. Esto no es un problema, por ejemplo:

[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
   [self doSomethingWithObject:obj];
}];

El bloque retiene self, pero selfno retiene el bloque. Si se libera uno u otro, no se crea ningún ciclo y todo se desasigna como debería.

Donde te metes en problemas es algo como:

//In the interface:
@property (strong) void(^myBlock)(id obj, NSUInteger idx, BOOL *stop);

//In the implementation:
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [self doSomethingWithObj:obj];     
}];

Ahora, su objeto ( self) tiene una strongreferencia explícita al bloque. Y el bloque tiene una fuerte referencia implícita a self. Eso es un ciclo, y ahora ninguno de los objetos se desasignará correctamente.

Porque, en una situación como esta, self por definición ya tiene una strongreferencia al bloque, generalmente es más fácil de resolver haciendo una referencia explícitamente débil selfpara que el bloque use:

__weak MyObject *weakSelf = self;
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [weakSelf doSomethingWithObj:obj];     
}];

¡Pero este no debería ser el patrón predeterminado que sigues cuando trates con bloques que llaman self! Esto solo debe usarse para romper lo que de otro modo sería un ciclo de retención entre uno mismo y el bloque. Si adoptara este patrón en todas partes, correría el riesgo de pasar un bloque a algo que se ejecutó después de que selffue desasignado.

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not retained!
  [weakSelf doSomething];
}];
jemmons
fuente
2
No estoy seguro de que A retenga B, B retenga A hará un ciclo infinito. Desde la perspectiva del recuento de referencias, el recuento de referencias de A y B es 1. Lo que causa un ciclo de retención para esta situación es cuando no hay otro grupo que tenga una referencia fuerte de A y B afuera: significa que no podemos alcanzar estos dos objetos (nosotros no puede controlar A para liberar B y viceversa), por lo tanto, A y B se refieren entre sí para mantenerse vivos.
Danyun Liu
@Danyun Si bien es cierto que un ciclo de retención entre A y B no es irrecuperable hasta que se hayan liberado todas las demás referencias a estos objetos, eso no lo hace menos un ciclo. Por el contrario, solo porque un ciclo en particular pueda ser recuperable no significa que esté bien tenerlo en su código. Los ciclos de retención son un olor a mal diseño.
jemmons
@ jemmons Sí, siempre debemos evitar un diseño de ciclo de retención tanto como podamos.
Danyun Liu el
1
@ Master Es imposible para mí decirlo. Depende completamente de la implementación de su -setCompleteionBlockWithSuccess:failure:método. Pero si paginatores propiedad de ellos ViewController, y estos bloques no se llaman después ViewController, se liberaría, usar una __weakreferencia sería el movimiento seguro (porque selfposee lo que posee los bloques, por lo que es probable que siga existiendo cuando los bloques lo llamen a pesar de que no lo retienen). Pero eso es un montón de "si". Realmente depende de lo que se supone que debe hacer.
jemmons
1
@Jai No, y esto está en el corazón del problema de administración de memoria con bloques / cierres. Los objetos se desasignan cuando nada los posee. MyObjecty SomeOtherObjectambos son dueños del bloque. Pero debido a que la referencia del bloque vuelve a MyObjectser weak, el bloque no es el propietario MyObject. Así, mientras que el bloque se garantiza que existirá mientras sea MyObject o SomeOtherObject existe, no hay garantía de que MyObjectexistirá siempre y cuando el bloque hace. MyObjectpuede ser completamente desasignado y, mientras SomeOtherObjectexista, el bloque seguirá allí.
jemmons
26

No tiene que usar siempre una referencia débil. Si su bloqueo no se retiene, sino que se ejecuta y luego se descarta, puede capturarse con fuerza, ya que no creará un ciclo de retención. En algunos casos, incluso desea que el bloque se retenga hasta que se complete el bloque para que no se desasigne prematuramente. Sin embargo, si captura el bloque con fuerza, y dentro de sí mismo, creará un ciclo de retención.

Leo Natan
fuente
Bueno, simplemente ejecuto el bloque como una devolución de llamada y no quisiera que se desasigne en absoluto. Pero parece que estaba creando ciclos de retención porque el controlador de vista en cuestión no se desasigna ...
the_critic
1
Por ejemplo, si tiene un elemento de botón de barra que retiene el controlador de vista y se captura con fuerza en ese bloque, habrá un ciclo de retención.
Leo Natan
1
@MartinE. Simplemente debe actualizar su pregunta con ejemplos de código. Leo tiene toda la razón (+1), que no necesariamente causa un ciclo de referencia fuerte, pero puede, dependiendo de cómo use estos bloques. Será más fácil para nosotros ayudarlo si proporciona fragmentos de código.
Rob
@LeoNatan Entiendo la noción de los ciclos de retención, pero no estoy muy seguro de lo que sucede en los bloques, así que eso me confunde un poco
the_critic
En el código anterior, su instancia propia se liberará una vez que finalice la operación y se libere el bloque. Debe leer sobre cómo funcionan los bloques y qué y cuándo capturan en su alcance.
Leo Natan
26

Estoy totalmente de acuerdo con @jemmons:

¡Pero este no debería ser el patrón predeterminado que sigues cuando trates con bloques que se autodenominan! Esto solo debe usarse para romper lo que de otro modo sería un ciclo de retención entre uno mismo y el bloque. Si adoptara este patrón en todas partes, correría el riesgo de pasar un bloque a algo que se ejecutó después de que se desasignara el self.

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not  retained!
  [weakSelf doSomething];
}];

Para superar este problema, se puede definir una referencia fuerte sobre el weakSelfinterior del bloque:

__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  MyObject *strongSelf = weakSelf;
  [strongSelf doSomething];
}];
Ilker Baltaci
fuente
3
¿StrongSelf no incrementaría el recuento de referencias para débilSelf? ¿Creando así un ciclo de retención?
mskw
12
Los ciclos de retención solo importan si existen en el estado estático del objeto. Mientras el código se está ejecutando y su estado está cambiando, las retenciones múltiples y posiblemente redundantes están bien. De todos modos, con respecto a este patrón, capturar una referencia fuerte no hace nada para el caso de autoasignación antes de que se ejecute el bloque, eso todavía puede suceder. Asegura que self no se desasigne al ejecutar el bloqueo. Esto es importante si el bloque realiza operaciones asincrónicas en sí, lo que da una ventana para que eso suceda.
Pierre Houston
@smallduck, tu explicación es genial. Ahora entiendo esto mejor. Los libros no cubren esto, gracias.
Huibin Zhang
55
Este no es un buen ejemplo de strongSelf, porque la adición explícita de strongSelf es exactamente lo que el tiempo de ejecución haría de todos modos: en la línea doSomething, se toma una referencia fuerte para la duración de la llamada al método. Si weakSelf ya estaba invalidado, la referencia fuerte es nula y la llamada al método es no operativa. Donde strongSelf ayuda es si tiene una serie de operaciones o accede a un campo miembro ( ->), donde desea garantizar que realmente obtuvo una referencia válida y mantenerla continuamente en todo el conjunto de operaciones, por ejemploif ( strongSelf ) { /* several operations */ }
Ethan
19

Como Leo señala, el código que agregó a su pregunta no sugeriría un ciclo de referencia fuerte (también conocido como ciclo de retención). Un problema relacionado con la operación que podría causar un fuerte ciclo de referencia sería si la operación no se libera. Si bien su fragmento de código sugiere que no ha definido su operación como concurrente, pero si lo ha hecho, no se publicará si nunca publicó isFinished, o si tuvo dependencias circulares, o algo así. Y si la operación no se lanza, el controlador de vista tampoco se lanzaría. Sugeriría agregar un punto de interrupción o NSLogen el deallocmétodo de su operación y confirmar que se llama.

Tu dijiste:

Entiendo la noción de retener ciclos, pero no estoy muy seguro de lo que sucede en los bloques, así que eso me confunde un poco

Los problemas del ciclo de retención (ciclo de referencia fuerte) que ocurren con los bloques son como los problemas del ciclo de retención con los que está familiarizado. Un bloque mantendrá referencias fuertes a cualquier objeto que aparezca dentro del bloque, y no liberará esas referencias fuertes hasta que se libere el bloque mismo. Por lo tanto, si las referencias de bloque self, o incluso solo hace referencia a una variable de instancia de self, que mantendrá una fuerte referencia a sí mismo, eso no se resolverá hasta que se libere el bloque (o en este caso, hasta que NSOperationse libere la subclase.

Para obtener más información, consulte la sección Evitar ciclos de referencia fuertes al capturar uno mismo del documento Programación con Objective-C: Trabajar con bloques .

Si su controlador de vista aún no se está lanzando, simplemente tiene que identificar dónde reside la referencia fuerte no resuelta (suponiendo que confirmó que NSOperationse está desasignando). Un ejemplo común es el uso de una repetición NSTimer. O algún delegateobjeto personalizado u otro que mantiene erróneamente una strongreferencia. A menudo puede usar instrumentos para rastrear dónde los objetos obtienen sus referencias fuertes, por ejemplo:

registro de recuentos de referencia en Xcode 6

O en Xcode 5:

registro de recuentos de referencia en Xcode 5

Robar
fuente
1
Otro ejemplo sería si la operación se retiene en el creador del bloque y no se libera una vez que finaliza. +1 en la buena redacción!
Leo Natan el
@LeoNatan De acuerdo, aunque el fragmento de código lo representa como una variable local que se lanzaría si usa ARC. ¡Pero tienes toda la razón!
Rob
1
Sí, solo estaba dando un ejemplo, como lo solicitó el OP en la otra respuesta.
Leo Natan
Por cierto, Xcode 8 tiene "Debug Memory Graph", que es una forma aún más fácil de encontrar referencias sólidas a objetos que no se han lanzado. Ver stackoverflow.com/questions/30992338/… .
Rob
0

Algunas explicaciones ignoran una condición sobre el ciclo de retención [Si un grupo de objetos está conectado por un círculo de relaciones fuertes, se mantienen vivos incluso si no hay referencias fuertes desde fuera del grupo.] Para obtener más información, lea el documento

Danyun Liu
fuente
-2

Así es como puedes usar el self dentro del bloque:

// llamada del bloque

 NSString *returnedText= checkIfOutsideMethodIsCalled(self);

NSString* (^checkIfOutsideMethodIsCalled)(*)=^NSString*(id obj)
{
             [obj MethodNameYouWantToCall]; // this is how it will call the object 
            return @"Called";


};
Ranjeet Singh
fuente