Слабая переменная NSString не равна нулю после установки единственной сильной ссылки на nil

У меня есть проблема с этим кодом:

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

Я ожидаю, что все указатели будут nil в настоящее время, но это не так, и я не понимаю, почему. Первый (сильный) указатель nil, а два других - нет. Почему это?

Ответ 1

TL; dr: Проблема в том, что строковый литерал никогда не освобождается, поэтому ваш слабый указатель все еще указывает на него.


Теория

Сильные переменные сохраняют значение, на которое они указывают.

Слабые переменные не сохраняют свое значение, и когда значение освобождается, они будут устанавливать указатель на нуль (чтобы быть безопасным).

Небезопасные недостижимые значения (как вы, вероятно, можете прочитать по имени) не сохранит значение, и если он будет освобожден, они ничего не сделают, потенциально указывая на плохую часть памяти


Литералы и константы

Когда вы создаете строку с помощью @"literal string", она становится строковым литералом, который никогда не изменится. Если вы используете одну и ту же строку во многих местах вашего приложения, это всегда один и тот же объект. Строковые литералы не исчезают. Использование [[NSString alloc] initWithString:@"literal string"] не изменит ситуацию. Поскольку он становится указателем на литеральную строку. Однако стоит отметить, что [[NSString alloc] initWithFormat:@"literal string"]; работает по-разному и освободит его строковый объект.

Строка за строкой:

__strong NSString *yourString = @"Your String"; 

Вы создаете сильный указатель на строку. Это гарантирует, что значение не исчезнет. В вашем случае это немного особенное, поскольку строка представляет собой строковый литерал, который технически не будет выпущен.

__weak NSString *myString = yourString;

Вы создаете слабый указатель на то же, что и ваш сильный указатель. Если в это время сильный указатель укажет на что-то еще, то значение, которое оно указывает, будет освобождено, тогда слабый указатель изменит его значение, чтобы он указывал на nil. Теперь он все же указывает на то же, что и сильный указатель.

yourString = nil;

Ваш сильный указатель указывает на nil. Ничто не указывает на старую строку, поэтому она должна быть выпущена, если бы не тот факт, что это была буквальная строка. Если вы попробовали точно такую ​​же вещь с другими созданными вами объектами, слабая переменная изменилась бы так, чтобы она указывала на nil. Но, поскольку строковый литерал является буквальным и не исчезает. Слабая переменная все равно укажет на нее.

__unsafe_unretained NSString *theirString = myString;

Создается новый недостижимый указатель, указывающий на ваш слабый указатель, указывающий на строковый литерал.

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

Вы печатаете все свои строки и путаетесь, почему первое значение nil, но другие два не являются.


Связанное чтение:

Какая разница между строковой константой и строковым литералом?

Ответ 2

Дэвид на 100% прав в своем ответе. Я добавил четыре явных примера, используя GHUnit.

Поведение классификатора жизненного цикла для ссылок на объекты.

Используя NSObject в качестве прокси для всех объектов, поведение определителей времени жизни будет таким, как ожидалось.

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

Поведение классификатора жизненного цикла для литерала NSString (@ "something" ).

Это в основном то же самое, что и test_usingNSObjects, но вместо использования NSObject используется NSString, которому назначена буквальная строка. Так как литеральные строки не уничтожаются, как другие объекты, наблюдаются разные поведения для переменных __weak и __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.");
}

Поведение классификатора жизненного цикла для нелитературных NSString s.

Это в основном то же самое, что и test_usingNSObjects, но вместо использования NSObject используется NSString, которому назначена нелитеративная строка. Так как нелитеративные строки уничтожаются, как и другие объекты, поведение такое же, как в 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;
}

NSString creation - literal vs nonliteral.

Показывает строки, созданные разными способами, если они являются буквальными или нелитеративными.

- (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, i.e. 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, i.e. isEqualToString: .
    GHAssertEqualStrings(literalString, @"literalString", nil);
    GHAssertEqualStrings(referenced, @"literalString", nil);
    GHAssertEqualStrings(copy, @"literalString", nil);
    GHAssertEqualStrings(initWithString, @"literalString", nil);
    GHAssertEqualStrings(initWithFormat, @"literalString", nil);
}

Ответ 3

слабое свойство будет установлено только на nil после того, как пул авторезистов будет слит.

попробовать:

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