Завжди передавати слабке посилання на себе в блок в 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);
    }
}

Якщо ви хочете глибокого дискурсу на цю тему, прочитайте dhoerl.wordpress.com/2013/04/23/…
David H

Відповіді:


721

Це допомагає не зосереджуватись на дискусії strongабо на її weakчастині. Замість цього зосередьтеся на частині циклу .

Цикл утримування - це цикл, який відбувається, коли Об'єкт A зберігає Об'єкт B, а Об'єкт B зберігає Об'єкт А. У цій ситуації, якщо випускається будь-який об'єкт:

  • Об'єкт A не буде розміщений, оскільки об'єкт B містить посилання на нього.
  • Але Об'єкт B ніколи не буде розміщений до тих пір, поки Об'єкт А не має посилання на нього.
  • Але Об'єкт A ніколи не буде розміщений, оскільки Об'єкт B має посилання на нього.
  • ad infinitum

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

Отже, ми хвилюємось - це збереження циклів , і немає нічого про блоки самі по собі, які створюють ці цикли. Це не проблема, наприклад:

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

2
Я не впевнений, що A зберегти B, B зберегти A буде нескінченним циклом. З точки зору опорного підрахунку, еталонний показник A і B дорівнює 1. Що спричиняє цикл утримування для цієї ситуації, коли немає жодної іншої групи, яка має чіткі посилання на A і B назовні - це означає, що ми не можемо досягти цих двох об'єктів (ми не можуть керувати A, щоб звільнити B, і навпаки), для цього A і B посилаються один на одного, щоб зберегти себе обох живими.
Даньюнь Лю

@Danyun Хоча це правда, що цикл збереження між A і B не підлягає відновленню, поки не будуть випущені всі інші посилання на ці об'єкти, це не робить його менш циклом. І навпаки, тільки те, що певний цикл може бути відновлений, не означає, що нормально його мати у своєму коді. Цикли утримування - це запах поганого дизайну.
jemmons

@jemmons Так, ми завжди повинні максимально уникати конструкції циклу збереження.
Даньюнь Лю

1
@Master Це неможливо сказати. Це повністю залежить від реалізації вашого -setCompleteionBlockWithSuccess:failure:методу. Але якщо paginatorвін належить ViewController, і ці блоки не викликаються після того, ViewControllerяк будуть випущені, використання __weakбезпечного руху - використання посилання - це безпечний хід (тому що selfволодіє річчю, яка володіє блоками, і тому, ймовірно, все ще буде, коли блоки називають це навіть якщо вони цього не зберігають). Але це багато "якщо" s. Це дійсно залежить від того, що це має робити.
jemmons

1
@Jai Ні, і це лежить в основі проблеми управління пам’яттю з блоками / закриттями. Об'єкти розставляються, коли їм нічого не належить. MyObjectі SomeOtherObjectобидва володіють блоком. Але з - за зворотне посилання блоку на MyObjectце weak, блок НЕ самостійно MyObject. Таким чином , в той час як блок гарантовано існувати до тих пір , як або MyObject чи SomeOtherObject існує, немає ніякої гарантії , що MyObjectбуде існувати до тих пір , поки блок робить. MyObjectможе бути повністю розміщений і, поки це SomeOtherObjectвсе ще існує, блок все ще буде там.
jemmons

26

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


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

1
Наприклад, якщо у вас є елемент кнопки панелі, який зберігається контролером перегляду, і ви сильно фіксуєте себе в цьому блоці, відбудеться цикл збереження.
Лев Натан

1
@MartinE. Вам слід просто оновити своє запитання зразками коду. Лев цілком правий (+1), що це не обов'язково викликає сильний еталонний цикл, але може, залежно від того, як ви використовуєте ці блоки. Нам буде легше допомогти вам, якщо ви надасте фрагменти коду.
Роб

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

У наведеному вище коді ваш власний екземпляр буде випущений, коли операція закінчиться і блок буде звільнений. Вам слід ознайомитись з тим, як працюють блоки та що і коли вони захоплюють у своїй області застосування.
Лев Натан

26

Я повністю згоден з @jemmons:

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

//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
Чи не зробив би сильний показник збільшення коефіцієнта опорного значення для слабкого Сельфа? Таким чином створюючи цикл утримування?
mskw

12
Зберігати цикли має значення лише в тому випадку, якщо вони існують у статичному стані об'єкта. У той час як код виконується, і його стан знаходиться в потоці, багатократне і, можливо, надмірне збереження є нормальним. У будь-якому випадку щодо цього шаблону, зафіксувавши чітке посилання, там нічого не робиться для випадку саморозгортання перед запуском блоку, що все ще може статися. Це гарантує, що самовизначення не буде розміщено під час виконання блоку. Це має значення, якщо блок виконує операції асинхронізації, даючи вікно для того, щоб це сталося.
П’єр Х'юстон

@smallduck, ваше пояснення чудово. Тепер я це краще розумію. Книги не висвітлювали це, дякую.
Хубібін Чжан

5
Це не є гарним прикладом strongSelf, оскільки явне додавання strongSelf - це саме те, що виконується в будь-якому разі: в рядку doSomething чітке посилання триває виклик методу. Якщо слабкуSelf вже було визнано недійсною, сильний коефіцієнт позначається нульовим, а виклик методу - необоротним. Там, де strongSelf допомагає, якщо у вас є ряд операцій або ви отримуєте доступ до поля члена ( ->), де ви хочете гарантувати, що ви насправді отримали дійсну посилання і постійно проводите її протягом усього набору операцій, наприкладif ( strongSelf ) { /* several operations */ }
Етан

19

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

Ти сказав:

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

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

Для отримання додаткової інформації див. Уникнення сильних еталонних циклів під час зйомки самостійного розділу Програмування за допомогою Цілі-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";


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