Подтвердить что ты не робот

Какова эталонная собственная семантика ReactiveCocoa?

Когда я создаю сигнал и ввожу его в область действия функции, его эффективное количество удержания равно 0 на Cocoa условные обозначения:

RACSignal *signal = [self createSignal];

Когда я подписываюсь на сигнал, он сохраняет подписчика и возвращает одноразовый ресурс, который в соответствии с соглашениями Cocoa также имеет значение удержания нуля.

RACDisposable *disposable = [signal subscribeCompleted:^ {
    doSomethingPossiblyInvolving(self);
}];

В большинстве случаев абонент будет закрываться и ссылаться на self или на его ivars или какую-либо другую часть охватывающей области. Поэтому, когда вы подписываетесь на сигнал, сигнал имеет ссылку на подписчика, и у подписчика есть ссылка на вас. И одноразовое, которое вы получаете взамен, имеет ссылку на сигнал.

disposable -> signal -> subscriber -> calling scope

Предположим, что вы держитесь за это одноразовое устройство, чтобы в какой-то момент отменить подписку (например, если сигнал извлекает данные из веб-службы, и пользователь переходит от экрана, отменяя ее намерение просматривать данные, являющиеся извлекаться).

self.disposeToCancelWebRequest = disposable;

В этот момент мы имеем круглую ссылку:

calling scope -> disposable -> signal -> subscriber -> calling scope

Ответственность за то, чтобы цикл был нарушен при отмене запроса или после завершения запроса.

 [self.disposeToCancelWebRequest dispose]
 self.disposeToCancelWebRequest = nil;

Обратите внимание, что вы не можете сделать это, когда self освобождается, потому что этого никогда не произойдет из-за цикла сохранения! Что-то также кажется подозрительным в нарушении цикла удержания во время обратного вызова подписчику, поскольку сигнал может быть удален из-за того, что его реализация все еще находится в стеке вызовов.

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

Как я могу думать о праве собственности при использовании RAC?

4b9b3361

Ответ 1

Управление памятью ReactiveCocoa довольно честно, но достойным конечным результатом является то, что вам не нужно сохранять сигналы для их обработки.

Если структура потребовала, чтобы вы сохранили каждый сигнал, было бы гораздо более громоздким в использовании, особенно для одноразовых сигналов, которые используются как фьючерсы (например, сетевые запросы). Вам нужно будет сохранить долгоживущий сигнал в свойстве, а затем также обязательно очистите его, когда закончите с ним. Не весело.

Подписчики

Прежде чем идти дальше, я должен указать, что subscribeNext:error:completed: (и все его варианты) создают неявный подписчик, используя данные блоки. Любые объекты, на которые ссылаются эти блоки, будут сохраняться как часть подписки. Как и любой другой объект, self не будет сохранен без прямой или косвенной ссылки на него.

(Основываясь на формулировке вашего вопроса, я думаю, вы уже это знали, но это может быть полезно для других.)

Конечные или короткоживущие сигналы

Наиболее важным ориентиром для управления памятью RAC является то, что подписка автоматически заканчивается после завершения или ошибки, а абонент удаляет. Чтобы использовать круглый ссылочный пример:

calling scope -> disposable -> signal -> subscriber -> calling scope

... это означает, что отношение signal -> subscriber срывается, как только заканчивается signal, прерывая цикл сохранения.

Это часто все, что вам нужно, потому что время жизни RACSignal в памяти, естественно, будет соответствовать логическому времени жизни потока событий.

Бесконечные сигналы

Бесконечные сигналы (или сигналы, которые живут так долго, что они могут быть бесконечными), однако, никогда не будут разрушаться естественным образом. Это то, где располагают одноразовые светильники.

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

Однако, как правило, , если вам нужно вручную управлять жизненным циклом подписки, возможно, лучший способ сделать то, что вы хотите.. Методы типа -take: или -takeUntil: будут обрабатывать удаление для вас, и вы получите более высокую абстракцию.

Сигналы, полученные из self

Здесь все еще немного сложного среднего дела. В любое время, когда время жизни сигнала привязано к области вызова, вам будет сложнее перерыв.

Это обычно происходит при использовании RACAble() или RACAbleWithStart() по ключевому пути, относящемуся к self, а затем применяя блок, который должен захватывать self.

Самый простой ответ - просто зафиксировать self слабо:

__weak id weakSelf = self;
[RACAble(self.username) subscribeNext:^(NSString *username) {
    id strongSelf = weakSelf;
    [strongSelf validateUsername];
}];

Или после импорта включенного EXTScope.h заголовка:

@weakify(self);
[RACAble(self.username) subscribeNext:^(NSString *username) {
    @strongify(self);
    [self validateUsername];
}];

(Замените __weak или @weakify на __unsafe_unretained или @unsafeify, соответственно, если объект не поддерживает слабые ссылки.)

Тем не менее, , вероятно, лучший шаблон, который вы могли бы использовать вместо этого. Например, приведенный выше пример может быть написан как:

[self rac_liftSelector:@selector(validateUsername:)
           withObjects:RACAble(self.username)];

или

RACSignal *validated = [RACAble(self.username) map:^(NSString *username) {
    // Put validation logic here.
    return @YES;
}];

Как и в случае с бесконечными сигналами, обычно есть способы избежать ссылки self (или любого объекта) из блоков в цепочке сигналов.


Вышеупомянутая информация - это все, что вам нужно, чтобы эффективно использовать ReactiveCocoa. Тем не менее, я хочу рассмотреть еще один момент, только для технически любопытных или для всех, кто заинтересован в участии в RAC:

Я также замечаю, что реализация сохраняет список глобальных активных сигналов процесса.

Это абсолютно верно.

Цель проекта "не сохранять необходимое" задает вопрос: откуда мы узнаем, когда сигнал должен быть освобожден? Что, если он был только что создан, избежал пула автозапуска и еще не был сохранен?

Реальный ответ: мы этого не делаем, но мы обычно можем предположить, что вызывающий абонент сохранит сигнал в текущей итерации цикла запуска, если они захотят его сохранить.

Следовательно:

  • Созданный сигнал автоматически добавляется к глобальному набору активных сигналов.
  • Сигнал будет ждать одного прохода основного цикла запуска, а затем удаляется из активного набора, если у него нет подписчиков. Если бы сигнал не был сохранен каким-либо образом, он бы снял с этого момента.
  • Если что-то подписалось в этой итерации цикла запуска, сигнал остается в наборе.
  • Позже, когда все подписчики ушли, снова запускается # 2.

Это может иметь неприятные последствия, если цикл цикла рекурсивно (например, в модальном цикле событий в OS X), но это делает жизнь сетевого пользователя намного проще для большинства или всех других случаев.

Ответ 2

Я пытаюсь решить тайну управления памятью ReactiveCocoa 2.5

RACSubject* subject = [RACSubject subject];
RACSignal* signal = [RACSignal return:@(1)];
NSLog(@"Retain count of RACSubject %ld", CFGetRetainCount((__bridge CFTypeRef)subject));
NSLog(@"Retain count of RACSignal %ld", CFGetRetainCount((__bridge CFTypeRef)signal));

Выход первой строки 1, а второй вывод строки 2. Кажется, что RACSignal будет где-то сохранено, а RACSubject - нет. Если вы явно не сохраняете RACSubject, он будет освобожден, когда программа выйдет из текущей области.