A variável NSString fraca não é nula depois de definir a única referência forte a zero

Eu tenho um problema com 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); 

Eu estou esperando que todos os pointers sejam nil neste momento, mas eles não são e eu não entendo o porquê. O primeiro ponteiro (forte) é nil mas os outros dois não são. Por que é que?

    tl; dr: O problema é que a string literal nunca é liberada, então seu ponteiro fraco ainda aponta para ela.


    Teoria

    Variáveis fortes reterão o valor para o qual elas apontam.

    Variáveis fracas não reterão seu valor e quando o valor for desalocado, elas definirão seu ponteiro como nulo (por segurança).

    Valores não seguros inseguros (como você provavelmente pode ler pelo nome) não reterão o valor e, se ele for desalocado, eles não farão nada a respeito, potencialmente apontando para uma parte ruim da memory.


    Literais e constantes

    Quando você cria uma string usando @"literal string" ela se torna uma string literal que nunca será alterada. Se você usar a mesma string em muitos lugares em seu aplicativo, será sempre o mesmo object. Literais de string não desaparecem. Usar o [[NSString alloc] initWithString:@"literal string"] não fará diferença. Desde que se torna um ponteiro para a cadeia literal. É no entanto digno de nota que [[NSString alloc] initWithFormat:@"literal string"]; funciona de forma diferente e irá liberar seu object string.

    Linha por linha:

     __strong NSString *yourString = @"Your String"; 

    Você está criando um ponteiro forte para uma string. Isso garantirá que o valor não desapareça. No seu caso, é um pouco especial, já que a string é uma string literal que tecnicamente não será liberada .

     __weak NSString *myString = yourString; 

    Você cria um ponteiro fraco para a mesma coisa que seu ponteiro forte. Se, nesse momento, o ponteiro forte apontasse para outra coisa, o valor para o qual ele está apontando seria desalocado, então o ponteiro fraco mudaria seu valor para que apontasse para nil . Agora ele ainda aponta para o mesmo que o ponteiro forte.

     yourString = nil; 

    Seu ponteiro forte aponta para nil . Nada aponta para a string antiga, então ela deve ser liberada se não fosse pelo fato de que era uma string literal . Se você tentou exatamente a mesma coisa com outros objects que você mesmo criou, a variável fraca mudaria de forma que nil para nil . Mas, como a string literal é literal e não desaparece. A variável fraca ainda apontará para ela.

     __unsafe_unretained NSString *theirString = myString; 

    Um novo ponteiro não-retido é criado, apontando para o seu ponteiro fraco que está apontando para o literal da string.

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

    Você imprime todas as suas cordas e fica confuso porque o primeiro valor é nil mas os outros dois não são.


    Leitura relacionada:

    Qual é a diferença entre uma constante de string e uma string literal?

    David está 100% correto em sua resposta. Acabei de adicionar quatro exemplos explícitos usando GHUnit .

    O comportamento do qualificador de tempo de vida para referências de objects.

    Usando NSObject como um proxy para todos os objects, o comportamento dos qualificadores de tempo de vida é o 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; } 

    O comportamento do qualificador de tempo de vida para NSString literais (@ “alguma coisa”).

    Isso é basicamente o mesmo que test_usingNSObjects , mas em vez de usar um NSObject , é usada uma NSString que recebe uma string literal. Como as strings literais não são destruídas como outros objects, comportamentos diferentes para variables __unsafe_unretained e __unsafe_unretained são observados.

     - (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."); } 

    O comportamento do qualificador de tempo de vida para NSString não literais.

    Isso é basicamente o mesmo que test_usingNSObjects , mas em vez de usar um NSObject , é usada uma NSString que recebe uma string não literal. Como as cadeias não literais são destruídas como outros objects, os comportamentos são os mesmos observados em 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; } 

    Criação de NSString – literal versus não literal.

    Mostra strings criadas de várias maneiras, se forem literais ou não literais.

     - (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); } 

    a propriedade fraca somente será definida como nula após o pool de autorelease ser drenado.

    experimentar:

     @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);