objective-c block reactive-cocoa ivar

objective c - Objective-C self->_ivar acceso con self explícito vs implícito->



block reactive-cocoa (1)

Todos los métodos de Objective-C se llaman con dos argumentos ocultos (de la "Guía de programación en tiempo de ejecución de Objective-C" ):

  • El objeto receptor
  • El selector para el método.

y un método puede referirse al objeto receptor como self (y a su propio selector como _cmd ).

Ahora _ivar es equivalente a self->_ivar donde self es este primer parámetro de función implícito . Mientras no defina una nueva variable self en un ámbito interno, _ivar == self->_ivar se _ivar == self->_ivar verdadero.

Si define una nueva variable en un ámbito interno, entonces tiene

  • El self definido localmente,
  • el "yo implícito" que es el primer parámetro de la función,

y _ivar todavía se refiere al "yo implícito"! Esto explica las advertencias del compilador en su bloque, que parecen contradecirse entre sí:

  • "Variable no utilizada ''yo'' ''se refiere al self definido localmente,
  • "Capturar el ''yo'' fuerte en este bloque ..." se refiere al "yo implícito" de la función.

El siguiente código demuestra también esto:

@interface MyClass : NSObject { NSString *_ivar; } @end @implementation MyClass - (void)test { _ivar = @"foo"; // Set instance variable of receiver { MyClass *self = [MyClass new]; // Redefine self in inner scope self->_ivar = @"bar"; // Set instance variable of redefined self NSLog(@"%@ - %@", self->_ivar, _ivar); // Output: bar - foo } } @end

Problema general

Hasta ahora, siempre pensé que self->_ivar es equivalente a _ivar . Hoy me enteré de que esto no es del todo cierto.

Vea, por ejemplo, el siguiente fragmento de código:

@interface TestClass : NSObject { NSString *_testIVar; } @end @implementation TestClass - (instancetype)init { if ((self = [super init])) { _testIVar = @"Testing Only"; } return self; } - (void)test { { NSInteger self = 42; NSLog(@"without arrow: %@", _testIVar); /* OK */ NSLog(@"with arrow: %@", self->_testIVar); /* COMPILER ERROR! */ } } @end

Aunque escondí el self original con algún NSInteger también llamado self , la sintaxis _testIVar implícita _testIVar todavía encuentra el yo "original" mientras que self->_testIVar obviamente no lo hace. En este último caso el compilador se queja correctamente con

El tipo de referencia de miembro ''NSInteger'' (también conocido como ''long'') no es un puntero

En el primer caso, sin embargo, simplemente funciona.

El problema del mundo real

Este ejemplo puede parecer bastante artificial pero no lo es en absoluto. Por ejemplo, el proyecto ExtObjC (utilizado por ReactiveCocoa ) define el muy útil @weakify(var) y @strongify(var) que ayuda a no capturar a self (y otros objetos) en bloques definiendo una sintaxis realmente práctica (no es necesario escribir el extraño y engorroso para escribir __weak typeof(self) weakSelf = self; [...] ^{ __strong typeof(self) strongSelf = weakSelf; [...] } más). Por ejemplo:

- (void)someMethod { @weakify(self); dispatch_async(self.someQueue, ^{ @strongify(self); NSLog(@"self @ %p", self); } }

Sin @weakify y @strongify , el bloque capturaría una fuerte referencia a self . Con el @weakify y @strongify no lo hace. Por lo tanto, la desasignación de self mismo no se pospondría hasta que se haya ejecutado el bloqueo. Sin embargo, la principal ventaja es que no es necesario que recuerdes usar weakSelf o strongSelf lugar de self porque el self original está oculto.

Eso es muy útil, ExtObjC implementa @weakify / @strongify generando algo similar a lo siguiente con macros:

- (void)someMethod { __weak typeof(self) _weakSelf = self; dispatch_async(self.someQueue, ^{ __strong typeof(self) self = _weakSelf; NSLog(@"self @ %p", self); } }

Justo, eso es aún mejor porque podemos seguir usándonos a nosotros self sin captar realmente una referencia fuerte a nosotros self . Sin embargo, tan pronto como utilicemos la sintaxis implícita de ivars de auto, se seguirá capturando una fuerte referencia al self "original".

- (void)someMethod { @weakify(self); dispatch_async(self.someQueue, ^{ @strongify(self); /* compiler warning: Unused variable self here!!! */ NSLog(@"self->_testIVar: %@", _testIVar); } }

Misceláneo

Cuando usamos ivars en bloques, definitivamente nos estamos capturando. Vea por ejemplo esta captura de pantalla: .

Otra cosa divertida de la captura de pantalla es que los mensajes de advertencia son

Variable no utilizada ''yo''

y en la linea de abajo

Capturar fuertemente el "yo" en este bloque es probable que conduzca a un ciclo de retención

Por eso creo que hay dos versiones de self :-)

Pregunta

La pregunta real aquí es: ¿Qué significa exactamente _testIVar ? ¿Cómo encuentra el puntero "original"?

Para aclarar (también vea mi captura de pantalla): Como señaló @MartinR (que es lo que pienso también), hay una versión especial de self que no se puede cambiar y solo se usa para acceso implícito-self-ivar. ¿Está eso documentado en alguna parte? Básicamente, ¿dónde se define a qué self refiere el self implícito? Parece comportarse de la misma manera que, por ejemplo, Java lo hace (con this ) pero con la diferencia de que this es una palabra clave reservada que no se puede anular.

La pregunta tampoco es cómo "arreglarlo", solo escribir self->_testIVar será lo que quiero en el ejemplo @weakify / @strongify . Es más, pensé que al usar @weakify / @strongify no puedes cometer el error de capturar a ti mismo de forma implícita, pero eso simplemente no parece ser el caso.