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.