Всегда передавать слабую ссылку на себя в блок в ARC?

252

Я немного запутался в использовании блока в 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);
    }
}
the_critic
источник
Если вы хотите подробно обсудить
David H

Ответы:

721

Это помогает не сосредоточиться на strongили weakчасти обсуждения. Вместо этого сосредоточьтесь на части цикла .

Сохраняет цикл представляет собой цикл , который происходит , когда объект А сохраняет объект B, и объект Б сохраняет объект A. В этой ситуации, если любой объект выпущен:

  • Объект A не будет освобожден, потому что объект B содержит ссылку на него.
  • Но Объект B никогда не будет освобожден, пока Объект A имеет ссылку на него.
  • Но Объект A никогда не будет освобожден, потому что Объект B содержит ссылку на него.
  • до бесконечности

Таким образом, эти два объекта будут просто зависать в памяти на протяжении всей жизни программы, даже если они должны, если все работает правильно, быть освобождены.

Итак, нас беспокоит то, что мы сохраняем циклы , и сами по себе блоки не создают эти циклы. Это не проблема, например:

[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's not retained!
  [weakSelf doSomething];
}];
jemmons
источник
2
Я не уверен, что A сохранит B, B сохранит A сделает бесконечный цикл. С точки зрения количества ссылок, количество ссылок A и B равно 1. Что вызывает цикл сохранения для этой ситуации, когда нет другой группы, имеющей сильную ссылку на A и B снаружи - это означает, что мы не можем достичь этих двух объектов (мы не может контролировать A, чтобы выпустить B и наоборот), поэтому A и B ссылаются друг на друга, чтобы поддерживать себя обоих.
Даньюнь Лю
@Danyun Хотя верно, что цикл сохранения между A и B не подлежит восстановлению, пока не будут освобождены все другие ссылки на эти объекты, это не делает его менее цикличным. И наоборот, только то, что определенный цикл может быть восстановим, не означает, что он может быть в вашем коде. Сохранение циклов - это запах плохого дизайна.
Джеммонс
@jemmons Да, мы всегда должны избегать сохранения дизайна цикла настолько, насколько мы можем.
Даньюнь Лю
1
@ Мастер Мне невозможно сказать. Это полностью зависит от реализации вашего -setCompleteionBlockWithSuccess:failure:метода. Но если paginatorон принадлежит ViewController, и эти блоки не были вызваны после того, как он ViewControllerбыл бы освобожден, использование __weakссылки было бы безопасным ходом (потому что selfвладеет вещью, которая владеет блоками, и поэтому, вероятно, все еще будет рядом, когда блоки вызывают ее хотя они не сохраняют это). Но это много "если". Это действительно зависит от того, что это должно делать.
Джеммонс
1
@Jai Нет, и это лежит в основе проблемы управления памятью с блоками / замыканиями. Объекты освобождаются, когда ничто не принадлежит им. MyObjectи SomeOtherObjectоба владеют блоком. Но поскольку ссылка на блок обратно MyObjectесть weak, блок не принадлежит MyObject. Таким образом , в то время как блок гарантированно существовать до тех пор , как либо MyObject или SomeOtherObject существует, нет никакой гарантии , что MyObjectбудет существовать до тех пор , пока блок делает. MyObjectможет быть полностью освобожден, и, пока он SomeOtherObjectеще существует, блок все еще будет там.
Джеммонс
26

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

Лео Натан
источник
Ну, я просто выполняю блок как обратный вызов, и я бы не хотел, чтобы self было вообще освобождено. Но создается впечатление, что я создавал циклы сохранения, потому что рассматриваемый контроллер представления не освобождается ...
the_critic
1
Например, если у вас есть элемент кнопки панели, который сохраняется контроллером представления, и вы сильно фиксируете себя в этом блоке, будет цикл сохранения.
Лев Натан
1
@MartinE. Вы должны просто обновить свой вопрос с примерами кода. Лев совершенно прав (+1), что это не обязательно вызывает сильный референсный цикл, но может, в зависимости от того, как вы используете эти блоки. Нам будет проще помочь вам, если вы предоставите фрагменты кода.
Роб
@LeoNatan Я понимаю понятие сохранения циклов, но я не совсем уверен, что происходит в блоках, так что это немного смущает меня
the_critic
В приведенном выше коде ваш собственный экземпляр будет освобожден после завершения операции и освобождения блока. Вы должны прочитать о том, как работают блоки и что и когда они отражают в своей области.
Лев Натан
26

Я полностью согласен с @jemmons:

Но это не должно быть шаблоном по умолчанию, который вы используете, когда имеете дело с блоками, вызывающими self! Это следует использовать только для разрыва того, что в противном случае было бы циклом сохранения между собой и блоком. Если бы вы использовали этот шаблон повсюду, вы бы рискнули передать блок чему-то, что было выполнено после освобождения self.

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not  retained!
  [weakSelf doSomething];
}];

Чтобы преодолеть эту проблему, можно определить сильную ссылку weakSelfвнутри блока:

__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  MyObject *strongSelf = weakSelf;
  [strongSelf doSomething];
}];
Илкер Балтачи
источник
3
Разве strongSelf не увеличит счетчик ссылок для weakSelf? Таким образом, создавая цикл сохранения?
Mskw
12
Сохранение циклов имеет значение только в том случае, если они существуют в статическом состоянии объекта. В то время как код выполняется и его состояние постоянно меняется, допускается многократное и, возможно, избыточное сохранение. В любом случае, в отношении этого паттерна получение сильной ссылки ничего не делает для случая, когда само освобождение освобождается до запуска блока, что все еще может произойти. Это гарантирует, что self не будет освобожден при выполнении блока. Это имеет значение, если блок сам выполняет асинхронные операции, давая окно для этого.
Пьер Хьюстон
@smallduck, ваше объяснение великолепно. Теперь я понимаю это лучше. Книги не освещали это, спасибо.
Хуйбин Чжан,
5
Это не хороший пример strongSelf, потому что явное добавление strongSelf - это именно то, что в любом случае будет делать среда выполнения: в строке doSomething указывается сильная ссылка на время вызова метода. Если strongSelf уже был признан недействительным, то сильная ссылка равна нулю, а вызов метода - нет. StrongSelf помогает, если у вас есть ряд операций или доступ к полю участника ( ->), где вы хотите гарантировать, что вы действительно получили действительную ссылку, и постоянно удерживать ее в течение всего набора операций, например,if ( strongSelf ) { /* several operations */ }
Итан
19

Как указывает Лео, код, который вы добавили в свой вопрос, не предлагал бы сильный ссылочный цикл (иначе, сохранить цикл). Одна из проблем, связанных с операцией, которая может привести к сильному циклу ссылок, может быть, если операция не освобождается. Хотя ваш фрагмент кода предполагает, что вы не определили свою операцию как параллельную, но если она у вас есть, она не будет выпущена, если вы никогда не публиковали isFinished, или если у вас были циклические зависимости или что-то в этом роде. И если операция не будет отменена, контроллер представления также не будет освобожден. Я бы предложил добавить точку останова или метод NSLogвашей операции deallocи подтвердить, что он вызывается.

Ты сказал:

Я понимаю понятие сохранения циклов, но я не совсем уверен, что происходит в блоках, так что это немного смущает меня

Проблемы с циклом сохранения (сильный ссылочный цикл), возникающие с блоками, аналогичны проблемам с циклом сохранения, с которыми вы знакомы. Блок будет поддерживать сильные ссылки на любые объекты, которые появляются внутри блока, и он не будет освобождать эти сильные ссылки, пока сам блок не будет освобожден. Таким образом, если блок ссылается selfили даже просто ссылается на переменную экземпляра self, которая будет поддерживать сильную ссылку на себя, это не разрешается до тех пор, пока блок не будет освобожден (или в этом случае, пока не NSOperationбудет освобожден подкласс.

Для получения дополнительной информации см. Раздел « Избегайте сильных эталонных циклов при захвате себя» в разделе « Программирование с помощью Objective-C: Работа с блоками» .

Если ваш контроллер представления все еще не освобождается, вы просто должны определить, где находится неразрешенная сильная ссылка (при условии, что вы подтвердили, что NSOperationосвобождается). Типичным примером является использование повторения NSTimer. Или некоторый пользовательский delegateили другой объект, который ошибочно поддерживает strongссылку. Вы можете часто использовать Инструменты, чтобы отследить, где объекты получают свои сильные ссылки, например:

количество ссылок на записи в Xcode 6

Или в Xcode 5:

количество ссылок на записи в Xcode 5

обкрадывать
источник
1
Другим примером может быть, если операция сохраняется в создателе блока и не освобождается после ее завершения. +1 на хороший пиши!
Лев Натан
@LeoNatan Согласен, хотя фрагмент кода представляет его как локальную переменную, которая будет освобождена, если он использует ARC. Но ты совершенно прав!
Роб
1
Да, я просто привел пример, как ОП запросил в другом ответе.
Лев Натан
Кстати, в Xcode 8 есть «График отладочной памяти», который является еще более простым способом найти надежные ссылки на объекты, которые не были выпущены. См. Stackoverflow.com/questions/30992338/… .
Роб
0

В некоторых объяснениях игнорируется условие о цикле сохранения [Если группа объектов связана кругом сильных отношений, они поддерживают друг друга, даже если нет сильных ссылок вне группы.] Для получения дополнительной информации прочитайте документ

Даньюнь Лю
источник
-2

Вот как вы можете использовать себя внутри блока:

// вызов блока

 NSString *returnedText= checkIfOutsideMethodIsCalled(self);

NSString* (^checkIfOutsideMethodIsCalled)(*)=^NSString*(id obj)
{
             [obj MethodNameYouWantToCall]; // this is how it will call the object 
            return @"Called";


};
Ранджит Сингх
источник