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

<p>боюсь, этот вопрос довольно простой, но я думаю, что он актуален для многих программистов objective-c, которые разбираются в блоках.</p><p>я слышал, что поскольку блоки захватывают локальные переменные, на которые они ссылаются, как копии <code>const</code>, использование <code>self</code> внутри блока может привести к циклу сохранения, если этот блок будет скопирован. итак, мы должны использовать <code>\_\_block</code>, чтобы заставить блок иметь дело непосредственно с <code>self</code> вместо того, чтобы копировать его.</p><pre><code>\_\_block typeof(self) bself = self; [someobject messagewithblock:^{ [bself dosomething]; }]; </code></pre><p>вместо просто</p><pre><code>[someobject messagewithblock:^{ [self dosomething]; }]; </code></pre><p>я хотел бы знать следующее: если это правда, могу ли я избежать уродства (кроме использования gc)?</p>

<p>строго говоря, тот факт, что это константная копия, не имеет ничего общего с этой проблемой. блоки сохранят все значения obj-c, которые были захвачены при их создании. так уж сложилось, что обходной путь для проблемы const-copy идентичен обходному пути для проблемы сохранения; а именно, используя класс хранения <code>\_\_block</code> для переменной.</p><p>в любом случае, чтобы ответить на ваш вопрос, здесь нет реальной альтернативы. если вы разрабатываете свой собственный api, основанный на блоках, и это имеет смысл, вы можете передать блоку значение <code>self</code> в качестве аргумента. к сожалению, это не имеет смысла для большинства api.</p><p>обратите внимание, что ссылка на ivar имеет точно такую же проблему. если вам нужно сослаться на ivar в вашем блоке, используйте вместо этого свойство или <code>bself->ivar</code>.</p><hr><p>приложение: при компиляции как arc, <code>\_\_block</code> больше не прерывает сохранение циклов. если вы компилируете для arc, вам нужно вместо этого использовать <code>\_\_weak</code> или <code>\_\_unsafe\_unretained</code>.</p>

<p>просто используйте:</p><pre><code>\_\_weak id weakself = self; [someobject somemethodwithblock:^{ [weakself someothermethod]; }]; </code></pre><p>для получения дополнительной информации: wwdc 2011 - <strong> блоки и grand central dispatch на практике </strong>.</p><p> <a href="https://developer.apple.com/videos/wwdc/2011/?id=308">https://developer.apple.com/videos/wwdc/2011/?id=308</a> </p><p><em> примечание: если это не сработает, вы можете попробовать </em></p><pre><code>\_\_weak typeof(self)weakself = self; </code></pre>

<p>это может быть очевидно, но вы должны использовать только уродливый псевдоним <code>self</code>, когда знаете, что получите цикл сохранения. если блок просто однократный, я думаю, вы можете спокойно игнорировать сохранение на <code>self</code>. плохой случай, когда у вас есть блок в качестве интерфейса обратного вызова, например. как здесь:</p><pre><code>typedef void (^buffercallback)(fullbuffer* buffer); @interface audioprocessor : nsobject {…} @property(copy) buffercallback bufferhandler; @end @implementation audioprocessor - (id) init { … [self setbuffercallback:^(fullbuffer* buffer) { [self whatever]; }]; … } </code></pre><p>здесь api не имеет особого смысла, но это имело бы смысл, например, при взаимодействии с суперклассом. мы сохраняем обработчик буфера, обработчик буфера сохраняет нас. сравните с чем-то вроде этого:</p><pre><code>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]; }]; } </code></pre><p>в этих ситуациях я не использую псевдоним <code>self</code>. вы получите цикл сохранения, но операция будет недолгой, и блок со временем выйдет из памяти, прерывая цикл. но мой опыт работы с блоками очень мал, и, возможно, псевдонимы <code>self</code> выходят в качестве наилучшей практики в долгосрочной перспективе.</p>

<p>опубликовать другой ответ, потому что это было проблемой и для меня. сначала я думал, что должен использовать blockself везде, где внутри блока есть собственная ссылка. это не тот случай, это только когда сам объект имеет блок в нем. и на самом деле, если вы используете blockself в этих случаях, объект может получить dealloc'd до того, как вы получите результат обратно из блока, а затем он потерпит крах, когда попытается вызвать его, поэтому ясно, что вы хотите, чтобы self сохранялось до ответа возвращается.</p><p>первый случай демонстрирует, когда произойдет цикл сохранения, поскольку он содержит блок, на который ссылается блок:</p><pre><code>#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]; } </code></pre><p>вам не нужен blockself во втором случае, потому что в вызывающем объекте нет блока, который вызовет цикл сохранения, когда вы ссылаетесь на self:</p><pre><code>#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; } </code></pre>

<p>помните также, что циклы сохранения могут произойти, если ваш блок ссылается на <i>другой</i> объект, который затем сохраняет <code>self</code>.</p><p>я не уверен, что сборка мусора может помочь в этих циклах сохранения. если объект, сохраняющий блок (который я назову объектом сервера), изживет <code>self</code> (клиентский объект), ссылка на <code>self</code> внутри блока не будет считаться циклической, пока сам удерживающий объект не будет освобожден. если объект сервера далеко переживает своих клиентов, у вас может быть значительная утечка памяти.</p><p>поскольку нет чистых решений, я бы порекомендовал следующие обходные пути. не стесняйтесь выбирать один или несколько из них, чтобы решить вашу проблему.</p><ul> <li>используйте блоки только для <i>завершение</i>, а не для открытых событий. например, используйте блоки для таких методов, как <code>dosomethingandwhendoneexecutethisblock:</code>, а не такие методы, как <code>setnotificationhandlerblock:</code>. блоки, используемые для завершения, имеют определенный срок службы и должны быть освобождены объектами сервера после их оценки. это предотвращает слишком длительный цикл сохранения, даже если он происходит.</li> <li>сделайте тот танец слабой ссылки, который вы описали.</li> <li>предоставьте метод для очистки вашего объекта перед его освобождением, который "отключает" объект от объектов сервера, которые могут содержать ссылки на него; и вызовите этот метод перед вызовом release для объекта. хотя этот метод вполне подходит, если у вашего объекта есть только один клиент (или он является единичным в некотором контексте), но он сломается, если у него несколько клиентов. вы в основном побеждаете здесь механизм сохранения счета; это похоже на вызов <code>dealloc</code> вместо <code>release</code>.</li> </ul><p>если вы пишете объект сервера, принимайте аргументы блока только для завершения. не принимайте аргументы блока для обратных вызовов, таких как <code>seteventhandlerblock:</code>. вместо этого вернитесь к классическому шаблону делегата: создайте формальный протокол и объявите метод <code>seteventdelegate:</code>. не оставляйте делегата. если вы даже не хотите создавать формальный протокол, примите селектор в качестве делегата обратного вызова.</p><p>и наконец, эта схема должна вызывать сигналы тревоги:</p><pre> - (void) dealloc { [myserverobject releasecallbackblocksforobject: self]; ... } </pre><p>если вы пытаетесь отсоединить блоки, которые могут ссылаться на <code>self</code> внутри <code>dealloc</code>, у вас уже есть проблемы. <code>dealloc</code> никогда не может быть вызвано из-за цикла сохранения, вызванного ссылками в блоке, что означает, что ваш объект просто будет просачиваться, пока объект сервера не будет освобожден.</p>

<p>вы можете использовать библиотеку libextobjc. он довольно популярен, например, в reactivecocoa. <a href="https://github.com/jspahrsummers/libextobjc">https://github.com/jspahrsummers/libextobjc</a> </p><p>он предоставляет 2 макроса @weakify и @strongify, так что вы можете иметь:</p><pre><code>@weakify(self) [someobject messagewithblock:^{ @strongify(self) [self dosomething]; }]; </code></pre><p>это предотвращает прямую сильную ссылку, поэтому мы не входим в цикл сохранения себя. кроме того, он предотвращает обнуление себя на полпути, но все равно правильно уменьшает счет удержания. подробнее по этой ссылке: <a href="http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html">http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html</a> </p>

<p>модификаторы <code>\_\_block \_\_unsafe\_unretained</code>, предложенные в <a href="https://stackoverflow.com/a/4352616/1331118">пост кевина</a> , могут привести к исключению плохого доступа в случае блока, выполняемого в другом потоке. лучше использовать только <strong> \_\_ block </strong> модификатор для временной переменной и сделать его нулевым после использования.</p><pre><code>\_\_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; }]; </code></pre>

<p>как насчет этого?</p><pre><code>- (void) foo { \_\_weak \_\_block me = self; myblock = ^ { [[me someprop] somemessage]; } ... } </code></pre><p>я больше не получаю предупреждение компилятора.</p>