2010-12-04 2 views
162

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

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

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

вместо того, чтобы просто

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

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

+3

Мне нравится называть мои `self` proxies` this`, чтобы перевернуть все вокруг. В JavaScript я называю свое `this` закрытием` self`, поэтому он чувствует себя хорошо и сбалансированно. :) – devios1 2013-07-30 22:43:14

+0

Интересно, есть ли какие-либо эквивалентные действия, которые нужно выполнить, если я использую блоки Swift – 2015-11-18 20:48:20

+0

@BenLu абсолютно! в Swift-закрытиях (и функции, которые передаются вокруг, которые упоминают себя неявно или явно) будут сохраняться. Иногда это желательно, и в других случаях он создает цикл (потому что сам закрытие становится собственностью самого себя (или принадлежит чему-то себе). Основная причина этого - из-за ARC. – Ethan 2016-04-29 14:11:59

ответ

166

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

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

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


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

+0

Нет проблем! Если бы это ответили на вопрос к вашему удовлетворению, я был бы признателен, если бы вы могли выберите это как правильный ответ на свой вопрос. Если нет, сообщите мне, как я могу лучше ответить на ваш вопрос. – 2010-12-04 08:24:05

+4

Нет проблем, Кевин. SO задерживает вас от немедленного ответа на вопрос, поэтому мне пришлось вернуться немного позже. Cheers. – 2010-12-04 08:47:57

+0

__unsafe_unretained id bself = self; – caleb 2012-04-27 16:47:27

21

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

9

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

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

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

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

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

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

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

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

19

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

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

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

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

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

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

61

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

__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; 
-1

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

1

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

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