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

Работа с потоками в мобильном приложении наиважнейшая тема и это все знают. Стандарнтыми инструментами для этого, являются GCD или NSOperation. Но при использовании ReactiveCocoa в нашем проекте, все становится несколько иначе. Нет, вам никто не запрещает использовать стандартные инструменты, но зачем? Мы в каждый блок будем пихать GCD? Для этого в ReactiveCocoa придумали весьма удобную реализацию.

Для работы с многопоточностью в ReactiveCocoa существует класс RACScheduler. По сути, это обертка над GCD… и имеет те же самые приоритеты потоков, что и у GCD:

typedef enum : long {
	RACSchedulerPriorityHigh = DISPATCH_QUEUE_PRIORITY_HIGH,
	RACSchedulerPriorityDefault = DISPATCH_QUEUE_PRIORITY_DEFAULT,
	RACSchedulerPriorityLow = DISPATCH_QUEUE_PRIORITY_LOW,
	RACSchedulerPriorityBackground = DISPATCH_QUEUE_PRIORITY_BACKGROUND,
} RACSchedulerPriority;

Рассмотрим основные методы RACScheduler, которые могут нам понадобиться при работе с ним:

Из названия, в принципе, становится ясно, что нам возвращается RACScheduler, который будет выполнять работу в главном потоке.

+ (RACScheduler *)mainThreadScheduler;

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

+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority;

Возвращает RACScheduler c приоритетом RACSchedulerPriorityDefault.

+ (RACScheduler *)scheduler;

Возвращает текущий RACScheduler из текущего NSThread.

+ (RACScheduler *)currentScheduler;

Блок, который RACSCheduler может выполнить где угодно. И к этому мы еще вернемся.

- (RACDisposable *)schedule:(void (^)(void))block;

Далее приведу основные функции для RACSignal, которые могут использоваться нами для управления многопоточностью: Данный метод RACSignal, говорит о том, что блоки получения новых значений в subscribeNext/doNext/subscribeError/etc. будут выполняться в том RACSCheduler, который мы вернем.

- (RACSignal *)deliverOn:(RACScheduler *)scheduler

Данный метод RACSignal, говорит о том, в каком RACScheduler будет выполняться блок, созданный при создании подписки (если мы говорим про ReactiveCocoa 2.5, то это: +[RACSignal createSignal:])

- (RACSignal *)subscribeOn:(RACScheduler *)scheduler

Приведу два коротких примера и мы на этом закончим

Создадим простой сигнал:

RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) {
// block executes on other thread with default priority
        for (NSInteger i = 0; i < 5000; i++) {
            NSLog(@"LOL");
            if (i == 5000) {
            [subscriber sendNext:@(YES)];
            }
        }
        return nil;
    }];

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

Создадим подписку на сигнал и укажем сигналу subscribeOn/deliverOn

[[[signal subscribeOn:[RACScheduler scheduler]]
                deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(id x) {
                // block executes on main thread
}];

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

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

dispatch_async(dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT), ^{
        // do something
        dispatch_async(dispatch_get_main_queue(), ^{
            // do something
        });
    });

И как это можно реализовать с RACSheduler: Как мы помним, при создании подписки на этот сигнал, мы указали, что он будет выполняться не в главном потоке. Но что делать, если в каком то месте, нам все понадобиться выполнить часть кода на главном потоке? Очень просто :) Здесь нам поможет - (RACDisposable *)schedule:(void (^)(void))block;

RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) {
// block executes on other thread with default priority
        for (NSInteger i = 0; i < 5000; i++) {
            NSLog(@"LOL");
            if (i == 5000) {
            [subscriber sendNext:@(YES)];
            }
        }
        [[RACScheduler mainThreadScheduler] schedule:^(void v) {
            // do something on main thread
        }];
        return nil;
    }];

На этом все :) Спасибо большое за внимание! И кстати, отличная новость: Состоялся долгожданный релиз RAC4! https://github.com/ReactiveCocoa/ReactiveCocoa/releases/tag/v4.0.0