La variable NSString débil no es nula después de establecer la única referencia fuerte a cero

Tengo un problema con este código:

__strong NSString *yourString = @"Your String"; __weak NSString *myString = yourString; yourString = nil; __unsafe_unretained NSString *theirString = myString; NSLog(@"%p %@", yourString, yourString); NSLog(@"%p %@", myString, myString); NSLog(@"%p %@", theirString, theirString); 

Estoy esperando que todos los punteros sean nil en este momento, pero no lo son y no entiendo por qué. El primer puntero (fuerte) es nil pero los otros dos no. ¿Porqué es eso?

tl; dr: El problema es que el literal de la cadena nunca se libera, por lo que el puntero débil todavía apunta a él.


Teoría

Las variables fuertes retendrán el valor que señalan.

Las variables débiles no retendrán su valor y cuando el valor sea desasignado, establecerán su puntero en cero (para estar seguros).

Los valores no retenidos inseguros (como probablemente pueda leer por el nombre) no retendrán el valor y si se desasignan, no hacen nada al respecto, lo que podría indicar una mala pieza de memoria


Literales y constantes

Cuando creas una cadena usando @"literal string" se convierte en una cadena literal que nunca cambiará. Si usa la misma cadena en muchos lugares de su aplicación, siempre es el mismo objeto. Los literales de cadena no desaparecen. Usar [[NSString alloc] initWithString:@"literal string"] no hará la diferencia. Dado que se convierte en un puntero a la cadena literal. Sin embargo, vale la pena señalar que [[NSString alloc] initWithFormat:@"literal string"]; funciona de manera diferente y liberará su objeto de cadena.

Linea por linea:

 __strong NSString *yourString = @"Your String"; 

Está creando un puntero fuerte a una cadena. Esto asegurará que el valor no desaparezca. En tu caso, es un poco especial, ya que la cadena es una cadena literal que técnicamente no se lanzará .

 __weak NSString *myString = yourString; 

Usted crea un puntero débil al mismo punto que su puntero fuerte. Si en este momento el puntero fuerte apuntaría a otra cosa, el valor al que apunta sería desasignado, entonces el puntero débil cambiaría su valor de modo que nil a nil . Ahora sigue apuntando a lo mismo que el puntero fuerte.

 yourString = nil; 

Su fuerte puntero apunta a nil . Nada apunta a la cadena anterior por lo que debería liberarse si no fuera por el hecho de que era una cadena literal . Si ha intentado exactamente lo mismo con otros objetos que creó usted mismo, la variable débil cambiaría de modo que apunta a nil . Pero, dado que el literal de la cadena es literal y no desaparece. La variable débil aún lo señalará.

 __unsafe_unretained NSString *theirString = myString; 

Se crea un nuevo puntero sin retener, apuntando a su puntero débil que apunta al literal de la cadena.

 NSLog(@"%p %@", yourString, yourString); NSLog(@"%p %@", myString, myString); NSLog(@"%p %@", theirString, theirString); 

Imprimes todas tus cadenas y te confundes por qué el primer valor es nil pero los otros dos no.


Lectura relacionada:

¿Cuál es la diferencia entre una constante de cadena y una cadena literal?

David es 100% correcto en su respuesta. Acabo de agregar cuatro ejemplos explícitos usando GHUnit .

Comportamiento del calificador de por vida para referencias de objetos.

Usando NSObject como proxy para todos los objetos, el comportamiento de los calificadores de por vida es el esperado.

 - (void) test_usingNSObjects { NSObject *value1 = [[NSObject alloc] init]; NSObject *value2 = [[NSObject alloc] init]; NSObject *value3 = [[NSObject alloc] init]; __strong NSObject *sRefToValue = value1; __weak NSObject *wRefToValue = value2; __unsafe_unretained NSObject *uRefToValue = value3; value1 = value2 = value3 = nil; GHAssertNotNil(sRefToValue, @"Strong reference to the object that was originally \ assigned to value1. Even though value1 was set to nil, the \ strong reference to the object keeps the object from being \ destroyed."); GHAssertNil(wRefToValue, @"Weak reference to the object that was originally assigned to \ value2. When value2 was set to nil, the weak reference does \ not prevent the object from being destroyed. The weak \ reference is also set to nil."); // Removing the #ifdef and #endif lines will result in a EXC_BAD_ACCESS // signal. Receiving a EXC_BAD_ACCESS signal is the expected behavior for // that code. #ifdef RECIEVE_EXC_BAD_ACCESS GHAssertNotNil(uRefToValue, @"Unsafe unretained reference to the object that was \ originally assigned to value3. When value3 was set to nil, \ the unsafe unretained reference does not prevent the object \ from being destroyed. The unsafe unretained reference is \ unaltered and the reference is invalid. Accessing the \ reference will result in EXC_BAD_ACCESS signal."); #endif // To avoid future EXC_BAD_ACCESS signals. uRefToValue = nil; } 

El comportamiento del calificador de por vida para NSString (@ “algo”).

Esto es básicamente lo mismo que test_usingNSObjects , pero en lugar de usar un NSObject , se NSString un NSString que se le asigna una cadena literal. Como las cadenas literales no se destruyen como otros objetos, se observan diferentes comportamientos para las variables __weak y __unsafe_unretained .

 - (void) test_usingLiteralNSStrings { NSString *value1 = @"string 1"; NSString *value2 = @"string 2"; NSString *value3 = @"string 3"; __strong NSString *sRefToValue = value1; __weak NSString *wRefToValue = value2; __unsafe_unretained NSString *uRefToValue = value3; value1 = value2 = value3 = nil; GHAssertNotNil(sRefToValue, @"Strong reference to the object that was originally \ assigned to value1. Even though value1 was set to nil, \ literal strings are not destroyed."); GHAssertNotNil(wRefToValue, @"Weak reference to the object that was originally assigned \ to value2. Even though value2 was set to nil, \ literal strings are not destroyed so the weak reference is \ still valid."); GHAssertNotNil(uRefToValue, @"Unsafe unretained reference to the object that was \ originally assigned to value3. Even though value3 was set \ to nil, literal strings are not destroyed so the unsafe \ unretained reference is still valid."); } 

El comportamiento del calificador de por vida para NSString s no NSString .

Esto es básicamente lo mismo que test_usingNSObjects , pero en lugar de usar un NSObject , se NSString un NSString que se le asigna una cadena no literal. Como las cadenas no literales se destruyen como otros objetos, los comportamientos son los mismos que se observaron en test_usingNSObjects .

 - (void) test_usingNonliteralNSStrings { NSString *value1 = [[NSString alloc] initWithFormat:@"string 1"]; NSString *value2 = [[NSString alloc] initWithFormat:@"string 2"]; NSString *value3 = [[NSString alloc] initWithFormat:@"string 3"]; __strong NSString *sRefToValue = value1; __weak NSString *wRefToValue = value2; __unsafe_unretained NSString *uRefToValue = value3; value1 = value2 = value3 = nil; GHAssertNotNil(sRefToValue, @"Strong reference to the object that was originally \ assigned to value1. Even though value1 was set to nil, the \ strong reference to the object keeps the object from being \ destroyed."); GHAssertNil(wRefToValue, @"Weak reference to the object that was originally assigned to \ value2. When value2 was set to nil, the weak reference does \ not prevent the object from being destroyed. The weak \ reference is also set to nil."); // Removing the #ifdef and #endif lines will result in a EXC_BAD_ACCESS // signal. Receiving a EXC_BAD_ACCESS signal is the expected behavior for // that code. #ifdef RECIEVE_EXC_BAD_ACCESS GHAssertNotNil(uRefToValue, @"Unsafe unretained reference to the object that was \ originally assigned to value3. When value3 was set to nil, \ the unsafe unretained reference does not prevent the object \ from being destroyed. The unsafe unretained reference is \ unaltered and the reference is invalid. Accessing the \ reference will result in EXC_BAD_ACCESS signal."); #endif // To avoid future EXC_BAD_ACCESS signals. uRefToValue = nil; } 

Creación de NSString – literal contra no literal.

Muestra cadenas creadas de varias maneras si son literales o no literales.

 - (void) test_stringCreation { NSString *literalString = @"literalString"; NSString *referenced = literalString; NSString *copy = [literalString copy]; NSString *initWithString = [[NSString alloc] initWithString:literalString]; NSString *initWithFormat = [[NSString alloc] initWithFormat:@"%@", literalString]; // Testing that the memory addresses of referenced objects are the same. GHAssertEquals(literalString, @"literalString", @"literal"); GHAssertEquals(referenced, @"literalString", @"literal"); GHAssertEquals(copy, @"literalString", @"literal"); GHAssertEquals(initWithString, @"literalString", @"literal"); GHAssertNotEquals(initWithFormat, @"literalString", @"nonliteral - referenced objects' memory addresses are \ different."); // Testing that the objects referenced are equal, ie isEqual: . GHAssertEqualObjects(literalString, @"literalString", nil); GHAssertEqualObjects(referenced, @"literalString", nil); GHAssertEqualObjects(copy, @"literalString", nil); GHAssertEqualObjects(initWithString, @"literalString", nil); GHAssertEqualObjects(initWithFormat, @"literalString", nil); // Testing that the strings referenced are the same, ie isEqualToString: . GHAssertEqualStrings(literalString, @"literalString", nil); GHAssertEqualStrings(referenced, @"literalString", nil); GHAssertEqualStrings(copy, @"literalString", nil); GHAssertEqualStrings(initWithString, @"literalString", nil); GHAssertEqualStrings(initWithFormat, @"literalString", nil); } 

la propiedad débil solo se establecerá en nil después de que se drenó el grupo de autorrelease.

tratar:

 @autoreleasepool { _strong NSString *yourString = @"Your String"; __weak NSString *myString = yourString; yourString = nil; __unsafe_unretained NSString *theirString = myString; } NSLog(@"%p %@", yourString, yourString); NSLog(@"%p %@", myString, myString); NSLog(@"%p %@", theirString, theirString);