Сохранить цикл на «себя» с блоками - PullRequest
165 голосов
/ 04 декабря 2010

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

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

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

вместо

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

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

Ответы [ 9 ]

168 голосов
/ 04 декабря 2010

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

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

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


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

64 голосов
/ 10 августа 2012

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

__weak id weakSelf = self;

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

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

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

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

__weak typeof(self)weakSelf = self;
22 голосов
/ 04 декабря 2010

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

19 голосов
/ 18 августа 2011

Отправка другого ответа, потому что это было проблемой и для меня. Сначала я думал, что должен использовать blockSelf везде, где внутри блока есть собственная ссылка. Это не тот случай, это только когда сам объект имеет блок в нем. И на самом деле, если вы используете blockSelf в этих случаях, объект может получить dealloc'd до того, как вы получите результат обратно из блока, а затем он потерпит крах, когда попытается вызвать его, поэтому ясно, что вы хотите, чтобы self сохранялось до ответа возвращается.

Первый случай показывает, когда произойдет цикл сохранения, поскольку он содержит блок, на который есть ссылка в блоке:

#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;
} 
9 голосов
/ 02 января 2011

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

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

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

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

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

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

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

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

1 голос
/ 22 декабря 2014

Вы можете использовать библиотеку 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 голос
/ 27 ноября 2012
Модификаторы

__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;
}];
0 голосов
/ 02 ноября 2012

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

0 голосов
/ 16 мая 2012

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

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

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

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

...