сильне захоплення себе в цьому блоці, ймовірно, призведе до циклу утримання


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;
користувач1845209

2
Що це: player(AVPlayer object)і timerDisp(UILabel)?
Карл Вейзей

Плеєр AVPlayer *; UILabel * timerDisp;
користувач1845209

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_unreished typeof (self) слабкийSelf = self; дякую за допомогу @Tim
користувач1845209

1
Хороша відповідь, але я беру з вами невелику проблему, кажучи: "Ви не можете посилатися на себе або властивості на себе з блоку, який буде сильно утримуватися". Це не зовсім суто. Будь ласка, дивіться мою відповідь нижче. Краще сказати: «Ви повинні дуже уважно ставитися до себе»,
Кріс Сутер,

8
Я не бачу циклу збереження в коді ОП. Блок сильно не утримується self, він зберігається основною чергою відправки. Я помиляюся?
erikprice

3
@erikprice: ти не помилився. Я інтерпретував це питання насамперед про помилку, представлену Xcode ("Як я можу уникнути цього попередження в xcode"), а не про фактичну наявність циклу збереження. Ви правильно сказали, що цикл збереження не видно лише з наданого фрагменту програми.
Тім

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];
    }
};

І ще одне дуже важливе, що потрібно пам’ятати: не використовуйте змінні екземпляри безпосередньо в блоці, використовуйте їх як властивості слабкого об’єкта, sample:

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;

Створіть чітке посилання на цю слабку версію як перший рядок у вашому блоці. Якщо самостійність все ще існує, коли блок починає виконуватись і не опустився до нуля, ця лінія забезпечує його збереження протягом усього часу виконання блоку.

Отже, вся справа була б така:

// 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


Швидке оновлення:

Наприклад, у швидкому простому методі з блоком успіху було б:

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

Коли ми називаємо цей метод і його потрібно використовувати selfв блоці успіху. Ми будемо використовувати функції [weak self]та guard letфункції.

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

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

Для отримання додаткової інформації ознайомтеся зі швидким посібником стилю


Що робити, якщо ви робили typeof(self) strongSelf = self;поза блоку (замість __слабкого), а потім у блоці, сказаному strongSelf = nil;після використання? Я не бачу, як ваш приклад гарантує, що слабкий зразок не буде нульовим до моменту виконання блоку.
Метт

Щоб уникнути можливих циклів утримування, ми встановлюємо слабку власну посилання поза будь-яким блоком, який використовує self у своєму коді. Ур у, ви повинні забезпечити виконання блоку. Інший блок ур-коду тепер відповідає за звільнення ур, раніше збереженої пам'яті.
Warif Akhand Rishi

@Matt ціль цього прикладу не полягає в тому, щоб зробити слабкуSelf збереженою. Мета полягає в тому, що якщо слабкийSelf не дорівнює нулю, зробіть чітке посилання всередині блоку. Отже, як тільки блок починає виконувати себе, self не стає нульовим всередині блоку.
Варіф Аханд Ріші

15

В іншій відповіді Тім сказав:

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

Це не зовсім так. З тобою добре це робити, доки в якийсь момент ви порушите цикл. Наприклад, скажімо, у вас є таймер, який спрацьовує, який містить блок, який зберігає самості, і ви також зберігаєте чітке посилання на таймер. Це цілком чудово, якщо ви завжди знаєте, що в якийсь момент ви знищите таймер і порушите цикл.

У моєму випадку я зараз отримав це попередження для коду, який робив:

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

Тепер я знаю, що кланг видасть це попередження лише у тому випадку, якщо він виявить, що метод починається з "set" (і ще один особливий випадок, який я тут не згадую). Для мене я знаю, що немає небезпеки виникнення циклу збереження, тому я змінив назву методу на "useY:" Звичайно, це може бути невідповідним у всіх випадках, і зазвичай ви хочете використовувати слабку посилання, але Я вважав, що варто відзначити моє рішення, якщо воно допомагає іншим.


4

Багато разів це насправді не цикл утримування .

Якщо ви знаєте, що це не так, вам не потрібно приносити у світ безрезультатних слабких людей.

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

Ось деякі директиви компілятора для видалення попередження з цього рядка коду:

#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

Нещодавно я зіткнувся з цим попередженням і хотів зрозуміти це трохи краще. Після трохи спроб і помилок я виявив, що це походить від того, що метод починається з "додавання" або "збереження". Об'єктив C розглядає назви методів, починаючи з "new", "alloc" тощо, як повернення збереженого об'єкта, але не згадує (що я можу знайти) нічого про "add" або "save". Однак якщо я використовую ім'я методу таким чином:

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

Я побачу попередження у рядку [самовиконання]. Однак це не буде:

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

Я буду йти вперед і використовую спосіб "__weak __typeof (self) слабкий" self "для посилання на мій об'єкт, але дуже не люблю робити це, оскільки це заплутає майбутнього мене та / або іншого розробника. Звичайно, я також не міг використовувати "додати" (або "зберегти"), але це гірше, оскільки це забирає сенс методу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.