Збережіть цикл на "self" за допомогою блоків


167

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

Я чув, що оскільки блоки захоплюють локальні змінні, на які посилаються в якості constкопій, використання selfблоку може призвести до збереження циклу, якщо цей блок буде скопійований. Отже, ми повинні використовувати, __blockщоб змусити блок безпосередньо працювати, selfа не копіювати його.

__block typeof(self) bself = self;
[someObject messageWithBlock:^{ [bself doSomething]; }];

замість просто

[someObject messageWithBlock:^{ [self doSomething]; }];

Що я хотів би знати, це наступне: якщо це правда, чи є спосіб я уникнути потворності (окрім використання GC)?


3
Мені подобається закликати своїх selfпроксі, thisщоб просто перевернути речі. У JavaScript я називаю своє thisзакриття self, тому він відчуває себе приємно і врівноважено. :)
devios1

Цікаво, чи потрібно робити якісь еквівалентні дії, якщо я використовую блоки Swift
Бен Лу

@BenLu абсолютно! у Swift закриттях (і функції, які передаються навколо цього згадування про себе неявно або явно) збережуть себе. Іноді цього хочеться, а в інший час це створює цикл (адже саме закриття стає власником власного «я» (або належить йому щось, що є власником). Основна причина цього відбувається через ARC.
Етан

1
Щоб уникнути проблем, відповідним способом визначення "self" для використання в блоці є "__typeof (self) __weak slabSelf = self;" щоб мати слабку орієнтир.
XLE_22

Відповіді:


169

Строго кажучи, той факт, що це копія const, не має нічого спільного з цією проблемою. Блоки зберігатимуть будь-які значення obj-c, які фіксуються під час їх створення. Так буває, що вирішення проблеми const-copy є ідентичним способу вирішення проблеми збереження; а саме, використовуючи __blockклас зберігання змінної.

У будь-якому випадку, щоб відповісти на ваше запитання, тут немає реальної альтернативи. Якщо ви розробляєте власний API на основі блоку, і це має сенс зробити це, ви могли б передати цей блок selfяк аргумент. На жаль, для більшості API це не має сенсу.

Зауважте, що посилання на ivar має саме таку проблему. Якщо вам потрібно посилатись на ivar у своєму блоці, використовуйте замість нього властивість або використовуйте bself->ivar.


Додавання: При компілюванні як ARC __blockбільше не відбувається перерв збереження циклів. Якщо ви компілюєте для ARC, вам потрібно використовувати __weakабо __unsafe_unretainedзамість цього.


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

4
Без проблем, Кевін. Тож затримує вас негайно вибрати відповідь на питання, тому мені довелося повернутися трохи пізніше. Ура.
Джонатан Стерлінг

__unsafe_unretain id bself = себе;
калеб

4
@JKLaiho: Звичайно, __weakдобре. Якщо ви знаєте на факт, що об'єкт не може бути поза сферою, коли виклик блоку, це __unsafe_unretainedстає дещо швидше, але в цілому це не змінить значення. Якщо ви все-таки використовуєте __weak, переконайтесь, що киньте його в __strongлокальну змінну і перевіряйте це на те, що не для цього, nilперш ніж робити щось із цим.
Лілі Баллард

2
@Rpranata: Так. __blockПобічний ефект від утримання та вивільнення був виключно зумовлений неможливістю правильно міркувати про це. З ARC компілятор здобув цю здатність, і __blockтепер він зберігає та випускає. Якщо вам потрібно цього уникнути, вам потрібно скористатися __unsafe_unretained, що дає вказівку компілятору не виконувати жодних затримок або випусків значення у змінній.
Лілі Баллард

66

Просто використовуйте:

__weak id weakSelf = self;

[someObject someMethodWithBlock:^{
    [weakSelf someOtherMethod];
}];

Для отримання додаткової інформації: WWDC 2011 - Блоки та грандіозні центральні відправки на практиці .

https://developer.apple.com/videos/wwdc/2011/?id=308

Примітка: якщо це не працює, ви можете спробувати

__weak typeof(self)weakSelf = self;

2
А ти випадково знайшов його :)?
Tieme

2
Ви можете подивитися відео тут - developer.apple.com/videos/wwdc/2011/…
nanjunda

Чи можете ви посилатись на "якийсь інший метод"? Чи було б самостійно посилатися на слабкого в цей момент, чи це також створить цикл утримування?
Орен

Привіт @ Орен, якщо ви спробуєте посилатися на себе всередині "someOtherMethod", ви отримаєте попередження Xcode. Мій підхід просто дає слабке посилання на себе.
3lvis

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

22

Це може бути очевидним, але ви повинні робити потворний selfпсевдонім лише тоді, коли знаєте, що у вас буде цикл збереження. Якщо блок - це лише один удар, то, я думаю, ви можете сміливо ігнорувати утримання self. Поганий випадок - коли у вас є блок, наприклад, інтерфейс зворотного дзвінка. Як тут:

typedef void (^BufferCallback)(FullBuffer* buffer);

@interface AudioProcessor : NSObject {…}
@property(copy) BufferCallback bufferHandler;
@end

@implementation AudioProcessor

- (id) init {
    
    [self setBufferCallback:^(FullBuffer* buffer) {
        [self whatever];
    }];
    
}

Тут API не має особливого сенсу, але це мало б сенс, наприклад, спілкуючись із суперкласом. Ми зберігаємо обробник буфера, обробник буфера зберігає нас. Порівняйте з чимось подібним:

typedef void (^Callback)(void);

@interface VideoEncoder : NSObject {…}
- (void) encodeVideoAndCall: (Callback) block;
@end

@interface Foo : NSObject {…}
@property(retain) VideoEncoder *encoder;
@end

@implementation Foo
- (void) somewhere {
    [encoder encodeVideoAndCall:^{
        [self doSomething];
    }];
}

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


6
Гарна думка. Це лише цикл утримування, якщо самоврядування підтримує блок живим. У випадку блоків, які ніколи не копіюються, або блоків із гарантованою обмеженою тривалістю (наприклад, блок заповнення анімації UIView), вам не доведеться турбуватися про це.
Лілі Баллард

6
В принципі ви маєте рацію. Однак, якби ви виконали код у прикладі, ви зірвались. Властивості блоку завжди повинні бути оголошені як copy, ні retain. Якщо вони просто retain, то немає жодної гарантії, що вони будуть переміщені зі стека, а це означає, що коли ви підете його виконати, його вже не буде. (а копіювання та вже скопійований блок оптимізовано для збереження)
Дейв ДеЛонг

Ах, звичайно, друкарська справа. Я пройшов retainдеякий час назад і швидко зрозумів те, що ви говорите :) Дякую!
Зуль

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

@Dave DeLong, Ні, це не вийде з ладу, оскільки @property (утримувати) використовується лише для посилання на об'єкт, а не для блоку. Тут взагалі не потрібно використовувати копію ..
DeniziOS

20

Опублікувати ще одну відповідь, тому що це була проблема і для мене. Спочатку я думав, що мені доведеться використовувати blockSelf де завгодно, коли всередині блоку є самопосилання. Це не так, це лише тоді, коли сам об’єкт має в ньому блок. Насправді, якщо ви використовуєте blockSelf в цих випадках, об’єкт може отримати dealloc'd, перш ніж повернути результат з блоку, і тоді він буде руйнуватися, коли він намагається викликати його, тому явно ви хочете, щоб самозбереження зберігалося до відповіді повертається.

Перший випадок показує, коли відбудеться цикл збереження, оскільки він містить блок, на який посилається в блоці:

#import <Foundation/Foundation.h>

typedef void (^MyBlock)(void);

@interface ContainsBlock : NSObject 

@property (nonatomic, copy) MyBlock block;

- (void)callblock;

@end 

@implementation ContainsBlock
@synthesize block = _block;

- (id)init {
    if ((self = [super init])) {

        //__block ContainsBlock *blockSelf = self; // to fix use this.
        self.block = ^{
                NSLog(@"object is %@", self); // self retain cycle
            };
    }
    return self;
}

- (void)dealloc {
    self.block = nil;
    NSLog (@"ContainsBlock"); // never called.
    [super dealloc];
} 

- (void)callblock {
    self.block();
} 

@end 

 int main() {
    ContainsBlock *leaks = [[ContainsBlock alloc] init];
    [leaks callblock];
    [leaks release];
}

У другому випадку вам не потрібен blockSelf, оскільки викликуючий об'єкт не має в ньому блоку, який спричинить цикл збереження при посиланні на себе:

#import <Foundation/Foundation.h>

typedef void (^MyBlock)(void);

@interface BlockCallingObject : NSObject 
@property (copy, nonatomic) MyBlock block;
@end

@implementation BlockCallingObject 
@synthesize block = _block;

- (void)dealloc {
    self.block = nil;
    NSLog(@"BlockCallingObject dealloc");
    [super dealloc];
} 

- (void)callblock {
    self.block();
} 
@end

@interface ObjectCallingBlockCallingObject : NSObject 
@end

@implementation ObjectCallingBlockCallingObject 

- (void)doneblock {
    NSLog(@"block call complete");
}

- (void)dealloc {
    NSLog(@"ObjectCallingBlockCallingObject dealloc");
    [super dealloc];
} 

- (id)init {
    if ((self = [super init])) {

        BlockCallingObject *myobj = [[BlockCallingObject alloc] init];
        myobj.block = ^() {
            [self doneblock]; // block in different object than this object, no retain cycle
        };
        [myobj callblock];
        [myobj release];
    }
    return self;
}
@end

int main() {

    ObjectCallingBlockCallingObject *myObj = [[ObjectCallingBlockCallingObject alloc] init];
    [myObj release];

    return 0;
} 

Це поширене оману і може бути небезпечним, оскільки це блокує слід зберегти, selfможуть не спричинити, коли люди надто застосовують це виправлення. Це хороший приклад уникнення циклів утримування у не-ARC-коді, дякую за публікацію.
Карл Вейзей

9

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

Я не впевнений, що збір сміття може допомогти у цих циклах утримання. Якщо об’єкт, що зберігає блок (який я буду називати об’єктом сервера), застарілий self(клієнтський об'єкт), посилання на selfблок всередині блоку не вважатиметься циклічним, поки сам звільняючий об'єкт не буде звільнений. Якщо серверний об’єкт сильно пережив своїх клієнтів, у вас може виникнути значна витік пам'яті.

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

  • Використовуйте блоки лише для завершення , а не для відкритих подій. Наприклад, використовувати блоки для таких методів doSomethingAndWhenDoneExecuteThisBlock:, а не таких методів setNotificationHandlerBlock:. Блоки, які використовуються для завершення, мають певний термін служби, і їх слід випускати об’єктами сервера після їх оцінки. Це заважає циклу утримування жити занадто довго, навіть якщо він стався.
  • Зробіть той слабкий еталонний танець, який ви описали.
  • Надайте спосіб очищення вашого об'єкта до його випуску, який "відключає" об'єкт від серверних об'єктів, які можуть містити посилання на нього; і викликати цей метод перед тим, як викликати випуск на об'єкт Хоча цей метод є прекрасним, якщо у вашого об’єкта є лише один клієнт (або він є однотонним у певному контексті), але він руйнується, якщо у нього є кілька клієнтів. Ви в основному переможете тут механізм підрахунку утримування; це схоже на дзвінки deallocзамість release.

Якщо ви пишете серверний об’єкт, приймайте блок-аргументи лише для його завершення. Не приймайте блокові аргументи для зворотних викликів, наприклад setEventHandlerBlock:. Натомість поверніться до класичного шаблону делегата: створіть офіційний протокол та рекламуйте setEventDelegate:метод. Не утримуйте делегата. Якщо ви навіть не хочете створити офіційний протокол, прийміть селектор як зворотний зворот делегата.

І нарешті, ця модель повинна звучати тривожно:

- (недійсна) угода {
    [myServerObject releaseCallbackBlocksForObject: self];
    ...
}

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


GC допомагає, якщо ви користуєтесь __weakналежним чином.
тс.

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

Просто всім відомо, що збирання сміття було вимкнено в OS X v10.8 на користь автоматичного підрахунку посилань (ARC) і планується видалити в наступній версії OS X ( developer.apple.com/library/mac/#releasenotes / ObjectiveC /… ).
Рікардо Санчес-Саес

1

__block __unsafe_unretainedмодифікатори, запропоновані у публікації Кевіна, можуть спричинити виняток з поганим доступом у випадку блоку, виконаного в іншому потоці. Краще використовувати лише модифікатор __block для змінної temp і зробити його нульовим після використання.

__block SomeType* this = self;
[someObject messageWithBlock:^{
  [this doSomething]; // here would be BAD_ACCESS in case of __unsafe_unretained with
                      //  multithreading and self was already released
  this = nil;
}];

Не було б дійсно безпечніше просто використовувати __weak замість __block, щоб уникнути необхідності зняття змінної після її використання? Я маю на увазі, це рішення чудово, якщо ви хочете зламати інші типи циклів, але, звичайно, я не бачу особливих переваг для "само" збереження циклів на ньому.
ale0xB

Ви не можете використовувати __weak, якщо ціль вашої платформи - iOS 4.x. Також іноді потрібно, щоб код у блоці був виконаний для дійсного об'єкта, а не для нуля.
b1gbr0

1

Ви можете використовувати бібліотеку libextobjc. Він досить популярний, його використовують, наприклад, у ReactiveCocoa. https://github.com/jspahrsummers/libextobjc

Він пропонує 2 макроси @weakify та @strongify, тож ви можете мати:

@weakify(self)
[someObject messageWithBlock:^{
   @strongify(self)
   [self doSomething]; 
}];

Це запобігає прямій сильній орієнтації, тому ми не потрапляємо в цикл збереження до себе. Крім того, це заважає самому собі стати нульовим на півдорозі, але все-таки належним чином зменшує кількість рахунків. Більше за цим посиланням: http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html


1
Перш ніж показати спрощений код, було б краще знати, що за ним стоїть, усі повинні знати два реальних рядки коду.
Алекс Ціо

0

Як щодо цього?

- (void) foo {
     __weak __block me = self;

     myBlock = ^ {
        [[me someProp] someMessage];
     }
     ...
 }

Я більше не отримую попередження про компілятор.


-1

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

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