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

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
}];
user1845209
источник
Есть 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_теперь содержит переменную. Попробуйте это контролировать.

iiFreeman
источник
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

Warif Akhand Rishi
источник
Что делать, если вы сделали 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
bshirley
источник
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», чтобы ссылаться на мой объект, но на самом деле мне не нравится делать это, так как это может сбить с толку будущего меня и / или другого разработчика. Конечно, я также не мог бы использовать «добавить» (или «сохранить»), но это еще хуже, поскольку он убирает смысл метода.

Рэй М.
источник