Всегда передавайте слабую ссылку на себя в блок в 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";


};