Исправить предупреждение «Захват [объекта] в этом блоке, вероятно, приведет к циклу сохранения» в коде с поддержкой ARC

141

В коде с поддержкой ARC, как исправить предупреждение о потенциальном цикле сохранения при использовании блочного API?

Предупреждение:
Capturing 'request' strongly in this block is likely to lead to a retain cycle

созданный этим фрагментом кода:

ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:request.rawResponseData error:nil];
    // ...
    }];

Предупреждение связано с использованием объекта requestвнутри блока.

Гийом
источник
1
Вам, вероятно, следует использовать responseDataвместо этого rawResponseData, проверьте документацию ASIHTTPRequest.
0xced 05

Ответы:

164

Отвечаю себе:

Насколько я понимаю, документация гласит, что использование ключевого слова blockи установка переменной на nil после использования его внутри блока должны быть в порядке, но все равно отображается предупреждение.

__block ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:request.responseData error:nil];
    request = nil;
// ....

    }];

Обновление: заставил его работать с ключевым словом _ weak вместо _block и использовать временную переменную:

ASIHTTPRequest *_request = [[ASIHTTPRequest alloc] initWithURL:...
__weak ASIHTTPRequest *request = _request;

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:request.responseData error:nil];
    // ...
    }];

Если вы хотите настроить таргетинг на iOS 4, используйте __unsafe_unretainedвместо __weak. Такое же поведение, но указатель остается висящим вместо того, чтобы автоматически устанавливаться на ноль при уничтожении объекта.

Гийом
источник
8
Судя по документации ARC, похоже, что вам нужно использовать __unsafe_unrehibited __block вместе, чтобы получить то же поведение, что и раньше, при использовании ARC и блоков.
Hunter
4
@SeanClarkHess: Когда я объединяю первые две строки, я получаю следующее предупреждение: «Назначение сохраненного объекта слабой переменной; объект будет освобожден после назначения»
Гийом
1
@Guillaume, спасибо за ответ, как-то я пропустил временную переменную, попробовал это, и предупреждения исчезли. Вы знаете, почему это работает? Это просто обман компилятора для подавления предупреждений или предупреждение действительно больше не действует?
Крис Вагнер,
2
Я разместил следующий вопрос: stackoverflow.com/questions/8859649/…
barfoon
3
Может кто-нибудь объяснить, зачем вам нужны ключевые слова __block и __weak? Я предполагаю, что создается цикл сохранения, но я его не вижу. И как создание временной переменной решает проблему?
user798719 01
50

Проблема возникает из-за того, что вы назначаете блок для запроса, в котором есть сильная ссылка на запрос. Блок автоматически сохранит запрос, поэтому исходный запрос не будет освобожден из-за цикла. Есть смысл?

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

ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...];
__weak ASIHTTPRequest *wrequest = request;

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:wrequest.rawResponseData error:nil];
    // ...
    }];
ЗаБлан
источник
__weak ASIHTTPRequest * wrequest = request; у меня не сработало. Ошибка выдачи Я использовал __block ASIHTTPRequest * blockRequest = request;
Ram G.
13

Это происходит из-за удержания себя в блоке. Доступ к блоку будет осуществляться от себя, а ссылка на себя находится в блоке. это создаст цикл сохранения.

Попробуйте решить эту проблему, создав слабую ссылку self

__weak typeof(self) weakSelf = self;

operationManager = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operationManager.responseSerializer = [AFJSONResponseSerializer serializer];
[operationManager setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {

    [weakSelf requestFinishWithSucessResponseObject:responseObject withAFHTTPRequestOperation:operation andRequestType:eRequestType];

} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    [weakSelf requestFinishWithFailureResponseObject:error withAFHTTPRequestOperation:operation andRequestType:eRequestType];
}];
[operationManager start];
HDdeveloper
источник
Это правильный ответ, и его следует отметить как таковой
Бенджамин
6

Иногда у компилятора xcode возникают проблемы с определением циклов сохранения, поэтому, если вы уверены, что не сохраняете блок завершения, вы можете установить флаг компилятора следующим образом:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"

-(void)someMethod {
}
GOrozco58
источник
1
Кто-то может возразить, что это плохой дизайн, но я иногда создаю независимые объекты, которые остаются в памяти до тех пор, пока они не закончат асинхронную задачу. Они сохраняются свойством завершенияBlock, которое содержит сильную ссылку на себя, создавая преднамеренный цикл сохранения. Блок завершения содержит self.completionBlock = nil, который освобождает блок завершения и прерывает цикл сохранения, позволяя освободить объект из памяти после завершения задачи. Ваш ответ полезен, чтобы заглушить предупреждения, которые появляются, когда я это делаю.
hyperspasm
1
честно говоря, шансы, что кто-то окажется прав, а компилятор ошибается, очень малы. Так что я бы сказал, что просто превзойти предупреждения - рискованное дело
Макс МакЛауд,
3

Когда я пробую решение, предоставленное Гийомом, в режиме отладки все нормально, но в режиме выпуска происходит сбой.

Обратите внимание, что не используйте __weak, но __unsafe_unrehibited, потому что моя цель - iOS 4.3.

Мой код вылетает, когда setCompletionBlock: вызывается для объекта "request": запрос был освобожден ...

Итак, это решение работает как в режиме отладки, так и в режиме выпуска:

// Avoiding retain cycle :
// - ASIHttpRequest object is a strong property (crashs if local variable)
// - use of an __unsafe_unretained pointer towards self inside block code

self.request = [ASIHttpRequest initWithURL:...
__unsafe_unretained DataModel * dataModel = self;

[self.request setCompletionBlock:^
{
    [dataModel processResponseWithData:dataModel.request.receivedData];        
}];
шквал2022
источник
Интересное решение. Вы выяснили, почему он вылетает в режиме Release, а не в Debug?
Валерио Сантинелли
2
ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...
__block ASIHTTPRequest *blockRequest = request;
[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:blockRequest.responseData error:nil];
    blockRequest = nil;
// ....

}];

какая разница между __weak и __block reference?

Эмиль Марашлиев
источник