сильный захват себя в этом блоке может привести к циклу сохранения


207

Как я могу избежать этого предупреждения в xcode. Вот фрагмент кода:

[player(AVPlayer object) addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
queue:nil usingBlock:^(CMTime time) {
    current+=1;

    if(current==60)
    {
        min+=(current/60);
        current = 0;
    }

    [timerDisp(UILabel) setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];///warning occurs in this line
}];

Есть timerDispсвойство в классе?
— Тим

Да, @property (неатомный, сильный) UILabel * timerDisp;
— user1845209

2
Что это: player(AVPlayer object)а timerDisp(UILabel)?
— Карл Визей

AVPlayer * player; UILabel * timerDisp;
— user1845209

5
Реальный вопрос заключается в том, как заставить это предупреждение замолчать без ненужной слабой ссылки на себя, когда вы знаете, что круговая ссылка будет нарушена (например, если вы всегда очищаете ссылку, когда завершается сетевой запрос).
— Гленн Мейнард

Ответы:


514

Захват selfздесь происходит с вашим неявным доступом к свойству self.timerDisp- вы не можете ссылаться на него selfили на свойства selfвнутри блока, который будет строго сохраняться self.

Вы можете обойти это, создав слабую ссылку selfперед доступом timerDispвнутри вашего блока:

__weak typeof(self) weakSelf = self;
[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
                                     queue:nil
                                usingBlock:^(CMTime time) {
                                                current+=1;

                                                if(current==60)
                                                {
                                                    min+=(current/60);
                                                    current = 0;
                                                }

                                                 [weakSelf.timerDisp setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];
                                            }];

13
Попробуйте использовать __unsafe_unretainedвместо этого.
— Тим

63
Решено. используйте вместо этого: __unsafe_unretained typeof (self) weakSelf = self; спасибо за помощь @Tim
— user1845209

1
Хороший ответ, но я возьму небольшую проблему с вами, говоря: «вы не можете ссылаться на себя или свойства на себя изнутри блока, который будет сильно сохраняться самим собой». Это не совсем верно. Пожалуйста, смотрите мой ответ ниже. Лучше сказать: «Вы должны быть очень осторожны, если говорите о себе…»
— Крис Сутер

8
Я не вижу цикл сохранения в коде OP. Блок не сильно сохраняется self, он сохраняется в основной очереди отправки. Я ошибся?
— erikprice

3
@erikprice: вы не ошиблись. Я интерпретировал этот вопрос, прежде всего, об ошибке, которую представляет XCode («Как я могу избежать этого предупреждения в XCode»), а не о фактическом наличии цикла сохранения. Вы правы, говоря, что цикл сохранения не очевиден только из предоставленного фрагмента OP.
— Тим

52
__weak MyClass *self_ = self; // that's enough
self.loadingDidFinishHandler = ^(NSArray *receivedItems, NSError *error){
    if (!error) {
       [self_ showAlertWithError:error];
    } else {
       self_.items = [NSArray arrayWithArray:receivedItems];
       [self_.tableView reloadData];
    }
};

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

self.loadingDidFinishHandler = ^(NSArray *receivedItems, NSError *error){
        if (!error) {
           [self_ showAlertWithError:error];
        } else {
           self_.items = [NSArray arrayWithArray:receivedItems];
           [_tableView reloadData]; // BAD! IT ALSO WILL BRING YOU TO RETAIN LOOP
        }
 };

и не забудьте сделать:

- (void)dealloc {
    self.loadingCompletionHandler = NULL;
}

другая проблема может появиться, если вы передадите слабую копию не сохраняемого кем-либо объекта:

MyViewController *vcToGo = [[MyViewCOntroller alloc] init];
__weak MyViewController *vcToGo_ = vcToGo;
self.loadingCompletion = ^{
    [vcToGo_ doSomePrecessing];
};

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


3
Это будет более сильный ответ, если вы также объясните это.
— Эрик Дж.

43

Лучшая версия

__strong typeof(self) strongSelf = weakSelf;

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

Так что все это будет так:

// Establish the weak self reference
__weak typeof(self) weakSelf = self;

[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
                                 queue:nil
                            usingBlock:^(CMTime time) {

    // Establish the strong self reference
    __strong typeof(self) strongSelf = weakSelf;

    if (strongSelf) {
        [strongSelf.timerDisp setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];
    } else {
        // self doesn't exist
    }
}];

Я прочитал эту статью много раз. Это отличная статья Эрики Садун о том, как избежать проблем при использовании блоков и NSNotificationCenter


Быстрое обновление:

Например, в swift простой метод с успешным блоком будет:

func doSomeThingWithSuccessBlock(success: () -> ()) {
    success()
}

Когда мы вызываем этот метод и нужно использовать selfв блоке успеха. Мы будем использовать [weak self]и guard letфункции.

    doSomeThingWithSuccessBlock { [weak self] () -> () in
        guard let strongSelf = self else { return }
        strongSelf.gridCollectionView.reloadData()
    }

Этот так называемый сильный-слабый танец используется популярным проектом с открытым исходным кодом. Alamofire .

Для получения дополнительной информации проверьте Swift-Style-Guide


Что делать, если вы сделали typeof(self) strongSelf = self;за пределами блока (вместо __weak), а затем в блоке strongSelf = nil;после использования? Я не вижу, как ваш пример гарантирует, что уязвимость Self не равна нулю к моменту выполнения блока.
— Мэтт

Чтобы избежать возможных циклов сохранения, мы устанавливаем слабую ссылку на себя вне любого блока, который использует self в своем коде. В вашем случае, вы должны убедиться, что блок выполняется. Другой блок вашего кода теперь отвечает за освобождение ранее сохраненной памяти.
— Вариф Акханд Риши

@Matt цель этого примера не состоит в том, чтобы сохранить слабый элемент. Цель состоит в том, чтобы, если strongSelf не равен nil, создать сильную ссылку внутри блока. Таким образом, как только блок начинает выполняться с собой, сам не становится нулевым внутри блока.
— Warif Akhand Rishi

15

В другом ответе Тим сказал:

Вы не можете ссылаться на себя или свойства на себя изнутри блока, который будет сильно сохраняться самим собой.

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

В моем случае только сейчас, у меня было это предупреждение для кода, который сделал:

[x setY:^{ [x doSomething]; }];

Теперь я знаю, что clang выдаст это предупреждение, только если обнаружит, что метод начинается с «set» (и еще одного особого случая, о котором я не буду здесь упоминать). Для меня, я знаю, что нет опасности существования цикла сохранения, поэтому я изменил имя метода на «useY:». Конечно, это может не подходить во всех случаях, и обычно вы захотите использовать слабую ссылку, но Я думал, что стоит отметить мое решение на случай, если оно поможет другим.


4

Много раз, это на самом деле не цикл сохранения .

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

Apple даже навязывает нам эти предупреждения с помощью API к ним UIPageViewController, который включает метод set (который вызывает эти предупреждения - как упоминалось в другом месте - думая, что вы устанавливаете значение для ивара, который является блоком) и блок обработчика завершения (в котором Вы, несомненно, будете обращаться к себе).

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

#pragma GCC diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
    [self.pageViewController setViewControllers:@[newViewController] direction:navigationDirection animated:YES completion:^(BOOL finished) {
        // this warning is caused because "setViewControllers" starts with "set…", it's not a problem
        [self doTheThingsIGottaDo:finished touchThePuppetHead:YES];
    }];
#pragma GCC diagnostic pop

1

Добавление двух центов на улучшение точности и стиля. В большинстве случаев вы будете использовать selfв этом блоке только одного или нескольких членов , скорее всего, просто для обновления слайдера. Кастинг selfизлишним. Вместо этого лучше быть явным и приводить только те объекты, которые вам действительно нужны внутри блока. Например, если это UISlider*, скажем, экземпляр _timeSlider, просто сделайте следующее до объявления блока:

UISlider* __weak slider = _timeSlider;

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

Полный пример:

UISlider* __weak slider = _timeSlider;
[_embeddedPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1)
     queue:nil
     usingBlock:^(CMTime time){
        slider.value = time.value/time.timescale;
     }
];

Кроме того, наиболее вероятно, что объект, приводимый к слабому указателю, уже является слабым указателем внутри, selfа также минимизирует или полностью исключает вероятность сохранения цикла. В приведенном выше примере _timeSliderэто свойство хранится как слабая ссылка, например:

@property (nonatomic, weak) IBOutlet UISlider* timeSlider;

С точки зрения стиля кодирования, как и в C и C ++, объявления переменных лучше читать справа налево. Декларирование SomeType* __weak variableв таком порядке читает более естественно справа налево , как: variable is a weak pointer to SomeType.


1

Недавно я столкнулся с этим предупреждением и хотел понять его немного лучше. После небольшой проб и ошибок я обнаружил, что это происходит из-за того, что метод начинается с «add» или «save». Цель C рассматривает имена методов, начинающиеся с «new», «alloc» и т. Д., Как возвращение сохраненного объекта, но не упоминает (что я могу найти) что-либо о «add» или «save». Однако, если я использую имя метода таким образом:

[self addItemWithCompletionBlock:^(NSError *error) {
            [self done]; }];

Я увижу предупреждение в строке [self done]. Однако это не будет:

[self itemWithCompletionBlock:^(NSError *error) {
    [self done]; }];

Я пойду дальше и буду использовать способ «__weak __typeof (self) weakSelf = self», чтобы ссылаться на мой объект, но на самом деле мне не нравится делать это, так как это может сбить с толку будущего меня и / или другого разработчика. Конечно, я также не мог бы использовать «добавить» (или «сохранить»), но это еще хуже, поскольку он убирает смысл метода.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.