it-swarm.com.ru

Сохранить цикл на «себя» с блоками

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

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

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

вместо просто

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

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

164
Jonathan Sterling

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

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

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


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

167
Lily Ballard

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

__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;
63
3lvis

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

22
zoul

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

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

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

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

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

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

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

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

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

9
Dave R

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

Модификаторы __block __unsafe_unretained, предложенные в пост Кевина , могут привести к исключению плохого доступа в случае блока, выполняемого в другом потоке. Лучше использовать только __ block модификатор для временной переменной и сделать его нулевым после использования.

__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;
}];
1
b1gbr0

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

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

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

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

0
Καrτhικ