Почему имеет смысл дублировать указатели для решения циклов сохранения на основе блоков в ARC? - PullRequest
4 голосов
/ 14 января 2012

В ARC, блок, как предполагается, вызывает цикл сохранения, если вы используете, например, self внутри блока.

Я видел обходной путь здесь ,например: enter image description here

Как этот обходной путь может предотвратить цикл сохранения?

weakRequest - это просто указатель на точно такой же объект, на который ссылается request.Когда ARC изменяет счет сохранения weakRequest или request, он воздействует на тот же объект.

Затем в блоке происходит странная вещь:

__strong ASIHTTPRequest *strongRequest = weakRequest;

Это эквивалентно высказыванию:

ASIHTTPRequest *strongRequest = weakRequest;
[strongRequest retain];

Но опять же: это один и тот же объект.Почему все эти разные имена переменных?Они просто указатели!

Я никогда особо не заботился о блоках и старался их избегать.Но теперь мне стало интересно, о чем все говорят, когда говорят «блок захватывает переменные».До сегодняшнего дня я думал, что это просто означает, что блок будет сохранять каждый используемый вами указатель, который был определен вне области действия блока, а это означает, что блок просто сохраняет любой объект, к которому вы прикоснулись, в своей области.

Я сделал этобыстрый тест:

UIView *v = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
[self.view addSubview:v];
v.backgroundColor = [UIColor orangeColor];

NSLog(@"self = %p", self); // 0x6a12a40

[UIView animateWithDuration:1.5 
                      delay:0
                    options:UIViewAnimationOptionAllowUserInteraction
                 animations:^{
                     UIViewController *my = self;
                     NSLog(@"my = %p", my); // 0x6a12a40
                     v.frame = CGRectMake(200, 200, 100, 100);
                 }
                 completion:nil];

Как вы видите, сам объект остается точно таким же.Блок не создает копию.Таким образом, я могу с уверенностью предположить, что все годы знания C и Objective-C все еще действительны:

ASIHTTPRequest *strongRequest = internetRequest;
ASIHTTPRequest *foo = strongRequest;
ASIHTTPRequest *bar = foo;

if (bar == internetRequest) {
    NSLog(@"exact same thing, of course");
}

Так что там происходит?Как это может разрешить сохранение счета, если все, что происходит, это создать разные указатели на один и тот же объект?Почему дополнительная миля создания этих указателей?

Разве это не одно и то же?

[request setCompletionBlock:^{
    NSString *respondeString = [request responseString];
    if ([_delegate respondsToSelector:@selector(pingSuccessful:)]) {
        [_delegate pingSuccessful:responseString];
    }
}];

В Objective-C должен быть какой-то секрет, объясняющий, почему дублирование указателей решает проблемы управления памятью здесь.Это просто не имеет никакого смысла для меня.

Ответы [ 3 ]

6 голосов
/ 14 января 2012

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

weakRequest - это просто указатель на точно такой же объект, на который ссылается запрос.Когда ARC изменяет количество сохраняемых уязвимых мест или запроса, это влияет на один и тот же объект.

Да, они оба указывают на один и тот же объект, но weakRequest имеет квалификатор владения __unsafe_unretained, что означает, что когда эта переменная захвачена блоком, ее счетчик хранения не изменяется.

Если бы блок request был захвачен блоком, он был бы сохранен, и у вас был бы цикл сохранения, независимо от того, используете ли вы ARC.

Преобразование обратно в __strongуказатель просто сохраняет этот объект живым на время выполнения блока.

2 голосов
/ 14 января 2012

Вы можете быть сбиты с толку, потому что происходит две разные вещи, чтобы предотвратить две разные проблемы.Вы цитировали эту строку:

__strong ASIHTTPRequest *strongRequest = weakRequest;

Эта строка не предотвращает цикл сохранения.

(Потенциальный) цикл сохранения является одной из проблем.Цикл сохранения будет содержать три объекта: self, ASIHTTPRequest и блок.Использование переменной weakRequest прерывает этот цикл, потому что блок захватывает weakRequest, которому не принадлежит объект ASIHTTPRequest.В терминах подсчета ссылок, назначение te weakRequest не увеличивает счетчик ссылок ASIHTTPRequest.

Указанная вами строка предназначена для предотвращения проблемы other , которая создаетсярешение первой проблемы.Другая проблема - потенциальный висячий указатель.Поскольку weakRequest не владеет ASIHTTPRequest, существует риск того, что во время выполнения блока завершения все владельцы ASIHTTPRequest выпустят его.Тогда weakRequest будет висящим указателем - указателем на объект, который был освобожден.Любое его использование может вызвать сбой или повреждение кучи.

В строке, которую вы указали, блок копирует weakRequest в strongRequest.Поскольку strongRequest равно __strong, компилятор генерирует код для сохранения (увеличения счетчика ссылок) ASIHTTPRequest и код для его освобождения в конце блока.Это означает, что даже если все другие владельцы ASIHTTPRequest освободят его во время работы блока, ASIHTTPRequest останется в живых, потому что блок временно сделал себя владельцем запроса.

Обратите внимание, что этоРешение проблемы с висящим указателем не является поточно-ориентированным.Если владельцы запроса могут освободить его из других потоков во время выполнения блока, существует условие гонки, которое может привести к зависанию указателя.Вот почему вы должны попытаться использовать __weak вместо __unsafe_unretained для слабых указателей: __weak ссылки могут быть скопированы в __strong ссылки без условия гонки.

2 голосов
/ 14 января 2012

Хорошо, вы указываете переменную как __weak точно, чтобы блок не сохранил ее, чтобы вы могли избежать цикла сохранения. Однако создание __strong Variable внутри блока и указание на переменную __weak совершенно излишне. Вы обозначаете его как слабый, чтобы блок не сохранил его. Создание нового и определение его как __strong ничего не значит, потому что нет экземпляра, в котором блок должен был бы его сохранить. __strong - это только ключевое слово компилятора, которое сообщает ARC о необходимости сохранить значение в случае необходимости ... и ARC никогда не найдет эту потребность, поскольку она уже передана в блок. В конце концов, вы можете просто использовать переменную weakRequest и покончить с переменной strongRequest.

...