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

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 замыкания (и функции, которые передаются, которые неявно или явно упоминают о себе) сохранят себя. Иногда это необходимо, и в других случаях он создает цикл (потому что само закрытие получает принадлежит самости (или принадлежит нечто само владеет) Основная причина это происходит из - за АРК..
Этан
1
Чтобы избежать проблем, подходящим способом определения «себя», который будет использоваться в блоке, является «__typeof (self) __weak слабыйSelf = self;» для того, чтобы иметь слабую ссылку.
XLE_22

Ответы:

169

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

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

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


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

Лили Баллард
источник
Нет проблем! Если это ответило на вопрос к вашему удовлетворению, я был бы признателен, если бы вы могли выбрать это как правильный ответ на ваш вопрос. Если нет, пожалуйста, дайте мне знать, как я могу ответить на ваш вопрос лучше.
Лили Баллард
4
Нет проблем, Кевин. ТАК задерживает выбор ответа на вопрос немедленно, поэтому мне пришлось вернуться чуть позже. Приветствия.
Джонатан Стерлинг
__unsafe_unretained id bself = self;
Калеб
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 - Блоки и Grand Central Диспетчер на практике .

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

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

__weak typeof(self)weakSelf = self;
3lvis
источник
2
А ты случайно нашел это :)?
Tieme
2
Вы можете проверить видео здесь - developer.apple.com/videos/wwdc/2011/...
nanjunda
Вы можете ссылаться на себя внутри "someOtherMethod"? Будет ли я ссылаться на себя в этой точке или это также создаст цикл удержания?
Орен
Здравствуйте, @Oren, если вы попытаетесь сослаться на себя внутри "someOtherMethod", вы получите предупреждение Xcode. Мой подход просто делает слабую ссылку на себя.
3
1
Я получил предупреждение только при ссылке на себя непосредственно внутри блока. Помещение себя в someOtherMethod не вызвало никаких предупреждений. Это потому, что XCode не достаточно умен, или это не проблема? Будет ли ссылка на self внутри someOtherMethod уже ссылаться на weakSelf, так как это то, на чем вы вызываете метод?
Орен
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в долгосрочной перспективе псевдонимы станут лучшей практикой.

Zoul
источник
6
Хорошая точка зрения. Это только цикл сохранения, если «Я» поддерживает блок живым. В случае блоков, которые никогда не копируются, или блоков с гарантированной ограниченной продолжительностью (например, блок завершения для анимации UIView), вам не нужно беспокоиться об этом.
Лили Баллард
6
В принципе ты прав. Однако, если бы вы выполнили код в примере, вы бы потерпели крах. Свойства блока всегда должны быть объявлены как copy, а не retain. Если они просто retain, то нет никакой гарантии, что они будут удалены из стека, а это означает, что когда вы приступите к его выполнению, его больше не будет. (и копирование, и уже скопированный блок оптимизирован для сохранения)
Дейв Делонг
Ах, конечно, опечатка. Я прошел через retainфазу некоторое время назад и быстро понял, что вы говорите :) Спасибо!
zoul
Я почти уверен, что retainполностью игнорируется для блоков (если только они не покинули стек copy).
Стивен Фишер
@Dave DeLong, нет, это не приведет к сбою, поскольку @property (retain) используется только для ссылки на объект, а не на блок. Нет необходимости вообще использовать копию здесь ..
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 во втором случае, потому что в вызывающем объекте нет блока, который вызовет цикл сохранения, когда вы ссылаетесь на 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;
} 
possen
источник
Это распространенное заблуждение и может быть опасным, потому что блокирует должны быть сохранены, selfне могут быть вызваны тем, что люди слишком применяют это исправление. Это хороший пример того, как избежать циклов сохранения в не-ARC-коде, спасибо за публикацию.
Карл Визи
9

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

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

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

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

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

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

- (void) dealloc {
    [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;
}];
b1gbr0
источник
Не было бы действительно безопаснее просто использовать __weak вместо __block, чтобы избежать необходимости обнуления переменной после ее использования? Я имею в виду, это решение прекрасно, если вы хотите разорвать другие типы циклов, но, конечно, я не вижу каких-либо особых преимуществ для того, чтобы "я" сохранял циклы на нем.
ale0xB
Вы не можете использовать __weak, если целью вашей платформы является iOS 4.x. Также иногда вам нужно, чтобы код в блоке был выполнен для действительного объекта, а не для nil.
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];
     }
     ...
 }

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

Καrτhικ
источник
-1

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

yijiankaka
источник