Слабая переменная 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);