Всегда передавайте слабую ссылку на себя в блок в ARC?
Я немного запутался в использовании блоков в Objective-C. В настоящее время я использую ARC, и у меня в моем приложении довольно много блоков, в настоящее время они всегда ссылаются на self
вместо его слабой ссылки. Может ли быть причиной того, что эти блоки сохраняют self
и не освобождают его от освобождения? Вопрос в том, должен ли я всегда использовать ссылку weak
self
в блоке?
-(void)handleNewerData:(NSArray *)arr
{
ProcessOperation *operation =
[[ProcessOperation alloc] initWithDataToProcess:arr
completion:^(NSMutableArray *rows) {
dispatch_async(dispatch_get_main_queue(), ^{
[self updateFeed:arr rows:rows];
});
}];
[dataProcessQueue addOperation:operation];
}
ProcessOperation.h
@interface ProcessOperation : NSOperation
{
NSMutableArray *dataArr;
NSMutableArray *rowHeightsArr;
void (^callback)(NSMutableArray *rows);
}
ProcessOperation.m
-(id)initWithDataToProcess:(NSArray *)data completion:(void (^)(NSMutableArray *rows))cb{
if(self =[super init]){
dataArr = [NSMutableArray arrayWithArray:data];
rowHeightsArr = [NSMutableArray new];
callback = cb;
}
return self;
}
- (void)main {
@autoreleasepool {
...
callback(rowHeightsArr);
}
}
Ответы
Ответ 1
Это помогает не сосредоточиться на части обсуждения strong
или weak
. Вместо этого сосредоточьтесь на части цикла.
Цикл удержания - это цикл, который происходит, когда объект А сохраняет объект B, а объект B сохраняет объект A. В этой ситуации, если один из объектов освобожден:
- Объект A не будет освобожден, поскольку объект B содержит ссылку на него.
- Но объект B никогда не будет освобожден, если объект A имеет ссылку на него.
- Но объект A никогда не будет освобожден, поскольку объект B содержит ссылку на него.
- ad infinitum
Таким образом, эти два объекта будут просто зависеть в памяти для жизни программы, даже если они должны, если все будет работать должным образом, освободиться.
Итак, нас беспокоит сохранение циклов, и нет ничего о самих блоках, которые создают эти циклы. Это не проблема, например:
[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
[self doSomethingWithObject:obj];
}];
Блок сохраняет self
, но self
не сохраняет блок. Если один или другой выпущен, цикл не создается и все освобождается, как и должно быть.
Где вы попадаете в неприятности, это что-то вроде:
//In the interface:
@property (strong) void(^myBlock)(id obj, NSUInteger idx, BOOL *stop);
//In the implementation:
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[self doSomethingWithObj:obj];
}];
Теперь ваш объект (self
) имеет явную ссылку strong
к блоку. И блок имеет скрытую ссылку на self
. Это цикл, и теперь ни один объект не будет освобожден должным образом.
Поскольку в такой ситуации self
по определению уже имеет ссылку strong
к блоку, его обычно легче разрешить, сделав явно слабую ссылку на self
для используемого блока:
__weak MyObject *weakSelf = self;
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[weakSelf doSomethingWithObj:obj];
}];
Но это не должно быть шаблоном по умолчанию, которым вы следуете при работе с блоками, которые вызывают self
! Это следует использовать только для того, чтобы сломать то, что в противном случае было бы циклом сохранения между я и блоком. Если бы вы использовали этот шаблон повсюду, вы рискуете передать блок тому, что было выполнено после того, как self
было освобождено.
//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
//By the time this gets called, "weakSelf" might be nil because it not retained!
[weakSelf doSomething];
}];
Ответ 2
Вам не обязательно всегда использовать слабую ссылку. Если ваш блок не сохраняется, а выполняется и затем отбрасывается, вы можете зафиксировать себя сильно, так как он не будет создавать цикл сохранения. В некоторых случаях вы даже хотите, чтобы блок удерживал себя до завершения блока, чтобы он не освобождался досрочно. Если, однако, вы сильно фиксируете блок и внутри самого захвата, он создаст цикл сохранения.
Ответ 3
Я полностью согласен с @jemmons:
Но это не должно быть шаблоном по умолчанию, который вы применяете при работе с блоками, которые вызывают себя! Это следует использовать только для разрыва того, что в противном случае было бы циклом сохранения между собой и блоком. Если бы вы применили этот шаблон повсюду, вы бы рискнули передать блок чему-то, что было выполнено после освобождения self.
//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
//By the time this gets called, "weakSelf" might be nil because it not retained!
[weakSelf doSomething];
}];
Чтобы преодолеть эту проблему, можно определить сильную ссылку на weakSelf
внутри блока:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
MyObject *strongSelf = weakSelf;
[strongSelf doSomething];
}];
Ответ 4
Как указывает Лео, код, добавленный в ваш вопрос, не предполагает сильного эталонного цикла (a.k.a., цикл сохранения). Одна из проблем, связанных с операциями, которые могут вызвать сильный опорный цикл, будет заключаться в том, что операция не будет выпущена. Хотя ваш фрагмент кода предполагает, что вы не определили свою операцию как параллельную, но если у вас есть, она не будет выпущена, если вы никогда не отправили isFinished
, или если у вас были круговые зависимости или что-то в этом роде. И если операция не будет выпущена, контроллер просмотра также не будет выпущен. Я бы предложил добавить точку останова или NSLog
в ваш метод работы dealloc
и подтвердить, что вызывается.
Ты сказал:
Я понимаю понятие циклов удержания, но я не совсем уверен, что происходит в блоках, так что меня немного смущает
Проблемы с циклом сохранения (сильным опорным циклом), возникающие в блоках, похожи на проблемы с циклом сохранения, с которыми вы знакомы. Блок будет поддерживать сильные ссылки на любые объекты, которые появляются в блоке, и он не выпустит эти сильные ссылки до тех пор, пока сам блок не будет выпущен. Таким образом, если ссылки блоков self
или даже просто ссылаются на переменную экземпляра self
, которая будет поддерживать сильную ссылку на self, которая не будет разрешена до тех пор, пока блок не будет выпущен (или в этом случае до подкласса NSOperation
.
Для получения дополнительной информации см. Избегайте сильных ссылочных циклов при разделе "Захват себя" в Программе с помощью Objective-C: Работа с документом блоков.
Если ваш контроллер просмотра по-прежнему не освобождается, вам просто нужно определить, где находится неразрешенная сильная ссылка (при условии, что вы подтвердили освобождение NSOperation
). Общим примером является использование повторяющегося NSTimer
. Или какой-то пользовательский delegate
или другой объект, который ошибочно поддерживает ссылку strong
. Вы часто можете использовать Инструменты для отслеживания, где объекты получают свои сильные ссылки, например:
![record reference counts in Xcode 6]()
Или в Xcode 5:
![record reference counts in Xcode 5]()
Ответ 5
В некоторых объяснениях игнорируется условие сохранения цикла [Если группа объектов связана кругом сильных отношений, они сохраняют друг друга живыми, даже если нет сильных ссылок извне группы.] Для получения дополнительной информации прочитайте document
Ответ 6
Лучше всего объяснить на примере. Глядя на следующее.
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
[self doSomethingWithObject:obj];
}];
Если "self" был контроллером представления, и он был отклонен контроллером родительского представления, я хотел бы, чтобы этот контроллер просмотра был освобожден из памяти. Однако вышеупомянутый метод будет содержать сильную ссылку на "я", и, таким образом, контроллер представления останется в памяти до тех пор, пока метод enumerateObjects полностью не завершит выполнение.
Если бы мы перешли в слабую ссылку на "я", это стало бы нулевым, когда диспетчер представлений был отклонен.
__weak CustomViewController *weakSelf = self;
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
[weakSelf doSomethingWithObject:obj];
}];
Ответ 7
Вот как вы можете использовать self внутри блока:
//вызов блока
NSString *returnedText= checkIfOutsideMethodIsCalled(self);
NSString* (^checkIfOutsideMethodIsCalled)(*)=^NSString*(id obj)
{
[obj MethodNameYouWantToCall]; // this is how it will call the object
return @"Called";
};