Слабые ссылки внутри блока

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

__weak Cell *weakSelf = self;
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        UIImage *image = /* render some image */
        /* what if by the time I get here self no longer exists? */
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            [weakSelf setImageViewImage:image];
        }];
    }];
    [self.renderQueue addOperation:op];

Итак, на мой вопрос, скажем, что к тому времени, когда изображение закончит рендеринг и эта строка вернется, объект Cell больше не существует (он был освобожден, возможно, из-за повторного использования соты, что немного сложно формализовать). Когда я перейду к доступу [weakSelf setImageViewImage:], это приведет к ошибке EXC_BAD_ACCESS?

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

Ответ 1

Итак, __weak является слабым этапом обнуления. Это означает, что во время вашей операции self действительно может быть освобожден, но все слабые ссылки на него (а именно weakSelf) будут обнулены. Это означает, что [weakSelf setImageViewImage:image] просто отправляет сообщение в nil, что безопасно; или, по крайней мере, он не должен вызывать EXC_BAD_ACCESS. (Кстати, если вы квалифицировали weakSelf как __unsafe_unretained, вы могли бы отправить сообщения освобожденному объекту.)

Итак, я сомневаюсь, что отправка сообщения в ссылку __weak вызывает сбой. Если вы хотите, чтобы self сохранялся на протяжении всей вашей операции, вы можете получить сильную ссылку на слабый в области блока:

__weak Cell *weakSelf = self;

NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    Cell *strongSelf = weakSelf; // object pointers are implicitly __strong
    // strongSelf will survive the duration of this operation.
    // carry on.
}];

Ответ 2

Если вы не используете слабый, вы создаете цикл сохранения, но цикл будет разорван, как только блоки закончат выполнение. Я, вероятно, не стал бы использовать слабых здесь.

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

Поскольку вы упоминаете повторное использование ячеек, я предполагаю, что ваш Cell является подклассом UITableViewCell. В этом случае ваш примерный код имеет серьезную проблему. UITableViewCell обычно не освобождаются. Они помещаются в очередь повторного использования соты. Таким образом, ваша переменная weakSelf не будет обнулена, потому что слабая ссылка только обнуляется, когда объект фактически освобождается.

К моменту окончания строки [weakSelf setImageViewImage:image] ячейка может быть повторно использована для представления другой строки в вашей таблице, и вы помещаете неправильное изображение в ячейку. Вы должны переместить код рендеринга изображения из класса Cell в класс данных datasource таблицы:

- (void)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    Cell *cell = // get a cell...

    [self startLoadingImageForIndexPath:indexPath];
    return cell;
}

- (void)startLoadingImageForIndexPath:(NSIndexPath *)indexPath {
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        UIImage *image = [self renderImageForIndexPath:indexPath];
        dispatch_async(dispatch_get_main_queue(), ^{
            Cell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
            [cell setImageViewImage:image];
        });
    }];
    [self.renderQueue addOperation:op];
}