Сохранять цикл на `self` с блоками

Я боюсь, что этот вопрос довольно простой, но я думаю, что это относится ко многим программистам Objective-C, которые попадают в блоки.

Что я слышал, так это то, что, поскольку блоки фиксируют локальные переменные, на которые ссылаются внутри них как const копии, использование self внутри блока может привести к циклу сохранения, если этот блок будет скопирован. Таким образом, мы должны использовать __block, чтобы заставить блок работать непосредственно с self вместо его копирования.

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

вместо

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

То, что я хотел бы знать, следующее: если это правда, есть ли способ избежать уродства (кроме использования GC)?

Ответ 1

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

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

Обратите внимание, что ссылка на ivar имеет ту же самую проблему. Если вам нужно ссылаться на ivar в своем блоке, используйте либо свойство, либо используйте bself->ivar.


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

Ответ 2

Просто используйте:

__weak id weakSelf = self;

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

Для получения дополнительной информации: WWDC 2011 - Блоки и Grand Central Dispatch на практике.

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

Примечание: если это не работает, вы можете попробовать

__weak typeof(self)weakSelf = self;

Ответ 3

Это может быть очевидно, но вам нужно сделать только уродливый псевдоним 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 в долгосрочной перспективе станет лучшей практикой.

Ответ 4

Проводка другого ответа, потому что это тоже было проблемой для меня. Первоначально я думал, что мне нужно использовать 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 во втором случае, потому что вызывающий объект не имеет в нем блока, который вызовет цикл сохранения, когда вы ссылаетесь на self:

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

Ответ 5

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

Я не уверен, что Garbage Collection может помочь в этих циклах сохранения. Если объект, сохраняющий блок (который я вызовет объект сервера), переживает self (клиентский объект), ссылка на self внутри блока не будет считаться циклической до тех пор, пока не будет освобожден сам сохраняющий объект. Если серверный объект далеко переживает своих клиентов, может возникнуть значительная утечка памяти.

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

  • Использовать блоки только для завершения, а не для открытых событий. Например, используйте блоки для таких методов, как doSomethingAndWhenDoneExecuteThisBlock:, а не такие методы, как setNotificationHandlerBlock:. Блоки, используемые для завершения, имеют определенные концы жизни и должны быть выпущены объектами сервера после их оценки. Это предотвращает длительный цикл жизни, даже если это происходит.
  • Сделайте этот славянский танец, который вы описали.
  • Предоставить метод очистки объекта перед его выпуском, который "отключает" объект от объектов сервера, которые могут содержать ссылки на него; и вызовите этот метод перед вызовом release на объекте. Хотя этот метод отлично подходит, если ваш объект имеет только один клиент (или один из них в каком-то контексте), но будет разбит, если у него несколько клиентов. Вы в основном побеждаете механизм учета сбережений; это похоже на вызов dealloc вместо release.

Если вы пишете объект сервера, принимайте аргументы блока только для завершения. Не принимайте аргументы блока для обратных вызовов, например setEventHandlerBlock:. Вместо этого вернитесь к классическому шаблону делегата: создайте формальный протокол и рекламируйте метод setEventDelegate:. Не сохраняйте делегата. Если вы даже не хотите создавать формальный протокол, принимайте селектор в качестве обратного вызова делегата.

И, наконец, этот шаблон должен вызывать сигналы тревоги:

- (void)dealloc {
    [myServerObject releaseCallbackBlocksForObject:self];
    ...
}

Если вы пытаетесь отцепить блоки, которые могут ссылаться на self изнутри dealloc, у вас уже проблемы. dealloc никогда не может быть вызван из-за цикла сохранения, вызванного ссылками в блоке, что означает, что ваш объект просто просачивается до тех пор, пока объект сервера не будет освобожден.

Ответ 6

__block __unsafe_unretained модификаторы, предлагаемые в Kevin post, могут привести к исключению плохого доступа в случае выполнения блока в другом потоке. Лучше использовать только модификатор __ 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;
}];

Ответ 7

Вы можете использовать библиотеку 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

Ответ 8

Как насчет этого?

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

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

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

Ответ 9

Блок: цикл сохранения будет происходить, поскольку он содержит блок, на который ссылается блок; Если вы сделаете копию блока и используете переменную-член, self сохранит.