IOS Правильное использование @weakify (self) и @strongify (self)
Я начинаю интегрировать libextobjc (https://github.com/jspahrsummers/libextobjc) в свое приложение iOS, прежде всего, для использования EXTScope @strongify
и @weakify
, но несколько вопросов, прежде чем переходить слишком глубоко в процесс.
Вот пример, который преднамеренно чрезмерно-сложный, чтобы попытаться понять, как справиться с этим:
- (void)someMethod {
if (self.someBOOL) {
_someObjectInstanceVar = [Object objectWithCompletionHandler:^{
// self reference #1
if (self.someProperty) {
// self reference #2
[[Async HTTPRequest] sendAWithID:self.property.id completionHandler:^(void (^)(NewObject *newObject) {
// self reference #3
[self showViewWithObject:newObject handler:^{
// self reference #4
[self reloadData];
}];
}];
}
}];
else {
[[Async HTTPRequest] sendBWithID:self.property.id completionHandler:^{
// self reference #5
[self reloadData];
}];
}
}
Я понимаю, что если я хочу сделать что-либо вроде асинхронного HTTP-запроса, а внутри справки self-обработчика завершения, например [self reloadData]
, мне не нужно ничего делать с сильным/слабым, поскольку сам блок запроса isn сохраняя блок завершения, поэтому нет проблем с циклами сохранения. В приведенном выше примере кода я думаю, что # 5 - это случай, когда циклы сохранения не являются проблемой.
Основная проблема - все объекты, которые принимают блок как параметр property/init, которые внутренне удерживают свойства блока. Внутри метода objectWithCompletionHandler
, где someObject
удерживается в блоке completeHandler как переменная экземпляра, есть несколько ссылок на self, которые, как я знаю, вызовут утечку. Мой главный вопрос в таком случае, как вам нужно обрабатывать weakify
и strongify
, чтобы сделать его "более безопасным"? Достаточно ли одного вызова @weakify и @strongify, как показано ниже:
- (void)someMethod {
@weakify (self);
_someObjectInstanceVar = [Object objectWithCompletionHandler:^{
@strongify(self);
...
}
Будет ли приведенная выше ссылка @strongify(self)
достаточной для использования для самостоятельных ссылок # 1, 2, 3 и 4, или мне нужно (и даже работать) получить новую слабую/сильную ссылку для использования внутри sendAWithID
и вложенный reloadData
?
РЕДАКТИРОВАТЬ: исправленный код, чтобы вопрос стал более понятным и исправить некоторые синтаксические ошибки.
Ответы
Ответ 1
Как работает @strongify
После вызова @strongify
, self
будет иметь другой адрес указателя внутри блока, чем вне блока. Это потому, что @strongify
каждый раз объявляет новую локальную переменную self
. (Вот почему он подавляет предупреждение -Wshadow
, которое будет "предупреждать, когда локальная переменная затеняет другую локальную переменную".) Стоит прочитать и понять реализацию эти функции. Поэтому, хотя имена одинаковы, рассматривайте их как отдельные ссылки strong
.
Используя @strongify
в вашем коде
Предполагая (что неверно), что каждое использование блока создало бы опорный цикл, вы могли бы:
- (void)someMethod {
if (self.someBOOL) {
@weakify(self);
_someObjectInstanceVar = [Object objectWithCompletionHandler:^{
@strongify(self);
// self reference #1
if (self.someProperty) {
@weakify(self);
// self reference #2
[[Async HTTPRequest] sendAWithID:self.property.id completionHandler:^(void (^)(NewObject *newObject) {
@strongify(self);
// self reference #3
@weakify(self);
[self showViewWithObject:newObject handler:^{
// self reference #4
@strongify(self);
[self reloadData];
}];
}];
}
}];
// etc…
}
Однако помните, что после первого использования @strongify
, self
будет ссылаться на локальные переменные стека. Обычно они будут уничтожены, когда область, в которой они определены, заканчивается (если вы не храните их в свойствах или не используете их во вложенном блоке). Поэтому, основываясь на коде, который вы показали, вам нужно только после // self reference #1
.
См. также
Чтение unit test, охватывающее @weakify
и @strongify
, поможет прояснить правильное использование этих функций.
Ответ 2
Чтобы ответить на вопрос о том, работает ли несколько экземпляров weakify/strongify на каждом вложенном уровне ваших блоков, да, да. Но нет необходимости делать это, потому что ваше первое определение @strongify уже определяет self для всей внутренней области вашего блока (и блоков, которые в нем вложены).
Однако, учитывая, что ваши блоки имеют разные сроки жизни, вы можете добавить @strongify для каждого вложенного блока, чтобы убедиться, что все они сохраняют свой собственный цикл сохранения в своей внутренней области.
Здесь поток ответов github, который объясняет этот случай:
https://github.com/jspahrsummers/libextobjc/issues/45
Ответ 3
Вызов "self" внутри блока, который удерживается "self" , приведет к "Сохранению циклов" и, следовательно, утечкам памяти. В идеале это выглядит так:
@interface A: NSObject // Some interface A
@property (nonatomic, copy, readwrite) dispatch_block_t someBlock; // See block is strongly retained here.
@end
*******************************************************
@implementation A
- (void) someAPI
{
__weak A * weakSelf = self; // Assign self to weakSelf and use it
// enter code here inside block to break retain cycles.
self.someBlock =
^{
A * strongSelf = weakSelf; // Assign weak self to strongSelf before
// using it. This is because weakSelf can go nil anytime and it may happen
// that only few lines from block get executed before weakSelf goes nil,
// and hence code may be in some bad state.
if (strongSelf != nil)
{
// Use strongSelf.
[strongSelf doSomethingAwesome];
[strongSelf doSomethingAwesomeAgain];
}
};
}
@end
Если блок не удерживается "self" , тогда его безопасно использовать "я" внутри блоков, и они не будут создавать циклы сохранения.
Примечание. Концепция управления памятью остается прежней с использованием библиотеки libextobjc.