ReactiveCocoa. Concurrency. Multithreading

от автора

Сегодня хотелось бы поговорить о работе с потоками в 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;     }]; 

ссылка на оригинал статьи https://habrahabr.ru/post/278817/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *