Rxswift что это
Перейти к содержимому

Rxswift что это

  • автор:

RxSwift часть 1

ReactiveX logo

Доброго времени суток, хабровчане. В этом цикле статей, я бы хотел рассказать о реактивном программировании, а именно о фреймворке
RxSwift. На Хабре и в сети были статьи по RxSwift, но, на мой взгляд, они слишком трудны для начинающих. Поэтому, если вы начинаете постигать реактивное программирование в iOS, то прошу под кат.

Начнем с определения, что такое реактивное программирование.

Реактивное программирование — парадигма программирования, ориентированная на потоки данных и распространение изменений.

Так гласит нам великая википедия.

Иными словами, в случае, когда мы программируем в императивном стиле, мы пишем в коде набор команд, которые должны выполняться последовательно. Реактивный стиль программирования придерживается несколько иных концепций. При реактивном стиле программирования наша программа является «слушателем» изменений состояний у наших наблюдаемых объектов. Звучит сложновато, но это не так, достаточно просто проникнуться этой концепции и все станет крайне легко и понятно, пока нет багов .

Я не буду расписывать как установить фреймворк, это легко сделать перейдя по ссылке. Давайте приступим к практике.

Observable

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

let observable = Observable.just("Первый observable")

BINGO! Мы создали первый observable.

и что?

Так как мы создали наблюдаемый объект, то логично, что нам необходимо создать объект, который будет наблюдать.

let observable = Observable.just("Первый observable") _ = observable.subscribe

в лог мы получаем следующее:

next(Первый observable) completed

completed?

Observable отправляет нам информацию о своих event’ах, есть всего 3 вида:

Вместе с next‘ом приходит элемент, который мы отправляли и все события посланные нами, error посылается как понятно из названия в случае ошибки, а completed в случае, когда наш observable отослал все данные и завершает работу.

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

_ = observable.subscribe(onNext: < (event) in print(event) >, onError: < (error) in print(error) >, onCompleted: < print("finish") >) < print("disposed") //о том, что это такое и зачем это мы поговорим позже >
Первая последовательность finish disposed

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

let sequence = Observable.of(1, 2, 4, 5, 6) _ = sequence.subscribe
next(1) next(2) . completed

Observable можно создать из массива значений.

let array = [1, 2, 3] let observable = Observable.from(array) _ = observable.subscribe
next(1) next(2) next(3) completed

У одного observable может быть сколь угодно много subscriber’ов. А теперь терминология, что такое Observable?

Observable — это основа всего Rx, которая асинхронно генерирует последовательность неизменяемых данных и позволяет подписываться на нее другим.

Disposing

Теперь, когда мы умеем создавать последовательность и подписываться на них, необходимо разобраться с такой штукой, как disposing.

Важно помнить, что Observable это «холодный» тип, то есть наш observable не «испускает» никаких событий, пока на него не подпишутся. Observable существует до тех пор, пока он не пошлет сообщение об ошибке (error) или сообщение о завершении (completed). Если мы хотим явно отменить подписку, то можем сделать следующее.

//вариант №1 //создали массив значений let array = [1, 2, 3] //создали observable из массива значений let observable = Observable.from(array) //создали подписку на observable let subscription = observable.subscribe < (event) in print(event) >//dispos'им нашу одноразовую подписку subscription.dispose()

Есть более красивый правильный вариант.

//создаем сумку "утилизации" let bag = DisposeBag() //создали массив значений let array = [1, 2, 3] //создали observable из массива значений let observable = Observable.from(array) //создали подписку на observable _ = observable.subscribe < (event) in print(event) >.disposed(by: bag)

Таким образом мы добавляем нашу подписку в сумку утилизации или в DisposeBag.
Для чего это нужно? Если вы, используя подписку, не добавите ее в сумку или явно не вызовете dispose, ну или в крайнем случае не приведете каким-то образом observable к завершению, то скорее всего вы получите утечку памяти. DisposeBag вы будете использовать очень часто в своей работе с RxSwift.

Operators

В функционально-реактивном программировании (ФРП далее) есть много встроенных операторов для трансформации элементов observable. Существует сайт rxmarbles, на нем можно посмотреть работу и эффект всех операторов, ну а мы все же рассмотрим некоторые из них.

Map

Оператор map используется очень часто и думаю, что знаком многим, с его помощью мы трансформируем все полученные элементы.
Пример:

let bag = DisposeBag() let array = [1, 2, 3] let observable = Observable.from(array).map < $0 * 2 >_ = observable.subscribe < (e) in print(e) >.disponsed(by: bag)

Что получим в консоли:

next(2) next(4) next(6) completed

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

let bag = DisposeBag() let observable = Observable.from(array) //создаем новый observable let transformObservable = observable.map < $0 * 2 >_ = transformObservable.subscribe < (e) in print(e) >.disposed(by: bag)
Что такое «$0»?

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

 //сокращенная форма let transformObservable = observable.map < $0 * 2 >//полная форма let transformObservable = observable.map < (element) ->Int in return element * 2 >

Согласитесь, что сокращенная форма записи куда проще, так?

Filter

Оператор filter позволяет нам отфильтровать испускаемые нашим observable’ом данные, то есть при подписке мы не будем получать ненужные нам значения.
Пример:

let bag = DisposeBag() let array = [1, 2, 3, 4, 5 , 6, 7] //создаем observable из массива let observable = Observable.from(array) //применяем функцию filter, сохраняя результат в новый observable let filteredObservable = observable.filter < $0 >2 > //подписка _ = filteredObservable.subscribe < (e) in print(e) >.disposed(by: bag)

Что мы получим в консоль?

next(3) next(4) next(5) . completed

Как мы видим, в консоли у нас только те значения, что удовлетворяют нашим условиям.

Кстати, операторы можно комбинировать, вот как это выглядело бы, если бы мы захотели применить сразу и оператор фильтрации и оператор map.

let bag = DisposeBag() let array = [1, 2, 3, 4, 5 , 6, 7] let observable = Observable.from(array) let filteredAndMappedObservable = observable .filter < $0 >2 > .map < $0 * 2 >_ = filteredAndMappedObservable.subscribe < (e) in print(e) >.disposed(by: bag)

Консоль:

next(6) next(8) next(10) next(12) next(14) completed

Distinct

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

let bag = DisposeBag() let array = [1, 1, 1, 2, 3, 3, 5, 5, 6] let observable = Observable.from(array) let filteredObservable = observable.distinctUntilChanged() _ = filteredObservable.subscribe < (e) in print(e) >.disposed(by: bag)

В консоль мы получим следующее:

next(1) next(2) next(3) next(5) next(6) completed

то есть в случае, если нынешний элемент последовательности идентичен предыдущему, то он пропускается и так происходит до тех пор, пока не появится отличный от предыдущего элемент, это очень удобно при работах скажем с UI, а именно с таблицей, в случае если нам пришли данные, такие же, как мы имеем сейчас, то reload’ить таблицу не следует.

TakeLast

Очень простой оператор takeLast, мы берем n-ое количество элементов с конца.

let bag = DisposeBag() let array = [1, 1, 1, 2, 3, 3, 5, 5, 6] let observable = Observable.from(array) let takeLastObservable = observable.takeLast(1) _ = takeLastObservable.subscribe < (e) in print(e) >.disposed(by: bag)

В консоль получим следующее:

next(6) completed

Throttle и Interval

Тут я решил взять сразу 2 оператора, просто потому что с помощью второго, легко можно показать работу первого.

Оператор throttle позволяет взять паузу между захватом передаваемых значений, очень просто пример, вы пишите реактивное приложение, используете строку поиска и не хотите каждый раз после ввода данных пользователем либо перезагружать таблицу, либо лезть на сервер, поэтому вы используете throttle и таким образом говорите, что хотите брать данные пользователя раз в 2 секунды (пример, можно поставить любой интервал) и снижаете расход ресурсов на лишнюю обработку, как это работает и описывается в коде? Смотрите ниже пример.

let bag = DisposeBag() //observable будет генерировать значение каждые 0.5 секунды с шагом 1 начиная от 0 let observable = Observable.interval(0.5, scheduler: MainScheduler.instance) let throttleObservable = observable.throttle(1.0, scheduler: MainScheduler.instance) _ = takeLastObservable.subscribe < (event) in print("throttle \(event)") >.disposed(by: bag)

В консоли будет:

throttle next(0) throttle next(2) throttle next(4) throttle next(6) . 

Оператор interval заставляет observable генерировать значения каждые 0,5 секунды с шагом 1 начиная с 0, вот такой простой таймер у Rx. Получается раз значения генерируются каждые 0,5 секунды, то в секунду генерируется 2 значения, нехитрая арифметика, а оператор throttle ждет секунду и берет последнее значение.

Debounce

Debounce очень похож на предыдущий оператор, но чуть более умнее, на мой взгляд. Оператор debounce ждет n-ое количество времени и в случае, если со старта его таймера не было изменений, то берет последнее значение, если же мы пошлем значение, то таймера перезапустится снова. Это как раз очень полезно для ситуации описанной в предыдущем примере, пользователь вводит данные, мы ждем когда он закончит (если пользователь бездействует секунду или полторы), а потом начинаем выполнять какие-то действия. Поэтому если мы просто поменяем оператор в предыдущем коде, то значений мы не получим в консоль, потому что debounce будет ждать секунду, но каждые 0,5 секунды будет получать новое значение и перезапускать свой таймер, таким образом мы ничего не получим. Посмотрим пример.

let bag = DisposeBag() let observable = Observable.interval(1.5, scheduler: MainScheduler.instance) let debounceObservable = observable.debounce(1.0, scheduler: MainScheduler.instance) _ = debounceObservable.subscribe(< (e) in print("debounce \(e)") >).disposed(by: bag)

На этом этапе предлагаю закончить с операторами, в фреймворке RxSwift их очень много, нельзя сказать, что все из них очень нужны в повседнейной жизни, но знать о их существовании все же надо, поэтому желательно ознакомиться с полным перечнем операторов на сайте rxmarbles.

Scheduler

Очень важная тема, которую в этой статье я бы хотел затронуть, это scheduler. Scheduler, позволяют нам запускать наши observable на определенных потоках и в них есть свои тонкости. Начнем, существует 2 вида установить observable scheduler — [observeOn]() и [subscribeOn]().

SubscribeOn

SubscribeOn отвечает за то, в каком потоке будет выполняться весь процесс observable до того момента, как его event’ы дойдут до обработчика (подписчика).

ObserveOn

Как можно догадаться observeOn отвечает за то, в каком потоке будут обрабатываться принятые подписчиком event’ы.

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

Давайте посмотрим, как это работает на примере:

let observable = Observable.create < (observer) ->Disposable in print("thread observable -> \(Thread.current)") observer.onNext(1) observer.onNext(2) return Disposables.create() >.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) _ = observable .observeOn(MainScheduler.instance) .subscribe( < (e) in print("thread ->\(Thread.current)") print(e) >)

В консоль мы получим:

thread observable ->  thread ->  next(1) thread ->  next(2)

Мы видим, что observable создавался в background потоке, а обрабатывали данные мы в main потоке. Это полезно при работе с сетью к примеру:

let rxRequest = URLSession.shared.rx.data(request: URLRequest(url: URL(string: "http://jsonplaceholder.typicode.com/posts/1")!)).subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) _ = rxRequest .observeOn(MainScheduler.instance) .subscribe

Таким образом запрос будет выполняться в background потоке, а вся обработка ответа будет происходить в main. На данном этапе пока рано говорить, что за rx метод у URLSession нарисовался вдруг, это будет рассмотрено позднее, данный код был приведен в качестве примера использования Scheduler, кстати, в консоль мы получим следующий ответ.

curl -X GET «http://jsonplaceholder.typicode.com/posts/1» -i -v Success (305ms): Status 200 **данные next(292 bytes)** thread -> данные completed thread ->

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

_ = rxRequest .observeOn(MainScheduler.instance) .subscribe < (event) in if (!event.isCompleted && event.error == nil) < let json = try? JSONSerialization.jsonObject(with: event.element!, options: []) print(json!) >print("data -> \(event)") print("thread -> \(Thread.current)") >

Мы проверяем, что event не сообщение о завершении работы observable и не ошибка пришедшая от него, хотя можно было реализовать другой метод подписки и обработать все эти виды event’ов отдельно, но это вы уже сможете сделать самостоятельно, а в консоль мы получим следующее.

curl -X GET «http://jsonplaceholder.typicode.com/posts/1» -i -v Success (182ms): Status 200 < body = "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"; title = "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"; userId = 1; >data -> next(292 bytes) thread -> data -> completed thread ->

Subjects

Переходим к горячему, а именно от «холодных» или «пассивных» observable к «горячим» или «активным» observable, которые зовутся subject’ами. Если до этого наши observable начинали свою работу только после подписки на них и у вас был вопрос в голове «ну и зачем мне все это надо?», то Subject’ы работают всегда и всегда шлют полученные данные.

Как это? В случае с observable мы заходили в поликлинику, шли к злой бабульке на ресепшене регистратуре, подходили и спрашивали в какой кабинет нам идти, тогда бабуленция нам отвечала, в случае с subject’ами, бабуленция стоит и слушает расписание и состояние врачей по больнице и как только получает информацию о перемещении какого-либо врача сразу говорит это, спрашивать у бабуленции что-то бесполезно, мы можем просто подойти, послушать ее, уйти, а она продолжит говорить, что-то увлекся со сравнениями, давайте уже к коду.

Создадим один subject и 2 подписчиков, первого создадим сразу после subject’а, пошлем subject’у значение, а потом создадим второго и пошлем еще парочку значений.

let subject = PublishSubject() subject.subscribe < (event) in print("первый подписчик \(event)") >subject.onNext(1) _ = subject.subscribe < (event) in print("второй подписчик \(event)") >subject.onNext(2) subject.onNext(3) subject.onNext(4)

Что мы увидим в консоли? правильно, первый успел получить первый event, а второй нет.

первый подписчик next(1) первый подписчик next(2) второй подписчик next(2) первый подписчик next(3) второй подписчик next(3) первый подписчик next(4) второй подписчик next(4)

Уже больше подходит под ваше представление о реактивном программировании?
Subject’ы бывают нескольких видов, все они отличаются тем, как они шлют значения.

PublishSubject — самый простой, ему без разницы на все, он просто рассылает всем подписчикам то, что ему пришло и забывает об этом.

ReplaySubject — а вот это самый ответственный, при создании мы указываем ему размер буфера (сколько значений будет запоминать), в результате он хранит в памяти последние n значений и посылает их сразу новому подписчику.

let subject = ReplaySubject.create(bufferSize: 3) subject.subscribe < (event) in print("первый подписчик \(event)") >subject.onNext(1) subject.subscribe < (event) in print("второй подписчик \(event)") >subject.onNext(2) subject.onNext(3) subject.subscribe < (event) in print("третий подписчик \(event)") >subject.onNext(4)

Смотрим в консоль

первый подписчик next(1) второй подписчик next(1) первый подписчик next(2) второй подписчик next(2) первый подписчик next(3) второй подписчик next(3) третий подписчик next(1) третий подписчик next(2) третий подписчик next(3) первый подписчик next(4) второй подписчик next(4) третий подписчик next(4)

BehaviorSubject — не такой пофигист, как предыдущий, он имеет стартовое значение и он запоминает последнее значение и посылает его сразу после подписки подписчика.

let subject = BehaviorSubject(value: 0) subject.subscribe < (event) in print("первый подписчик \(event)") >subject.onNext(1) subject.subscribe < (event) in print("второй подписчик \(event)") >subject.onNext(2) subject.onNext(3) subject.subscribe < (event) in print("третий подписчик \(event)") >subject.onNext(4)
первый подписчик next(0) первый подписчик next(1) второй подписчик next(1) первый подписчик next(2) второй подписчик next(2) первый подписчик next(3) второй подписчик next(3) третий подписчик next(3) первый подписчик next(4) второй подписчик next(4) третий подписчик next(4)

Заключение

Это была вводная статья, написанная для того, чтобы вы знали основы и могли в последующем отталкиваться от них. В следующих статьях мы рассмотрим как работать с помощью RxSwift с UI компонентами iOS, создание расширений для UI компонентов.

Не RxSwift’ом едины

Реактивное программирование реализовано не только в библиотеке RxSwift, есть несколько реализаций, вот самые популярные из них ReacktiveKit/Bond, ReactiveSwift/ReactiveCocoa. У всех у них есть небольшие различия в реализации под капотом, но на мой взгляд лучше начинать свое познание «реактивщины» именно с RxSwift, так как он наиболее популярный среди них и по нему будет больше ответов в великом гугле, но после того, как вы вникните в суть данной концепции, можно выбирать библиотеки на свой вкус и цвет.
Автор статьи: Гречихин Павел

  • реактивное программирование
  • swift
  • ios
  • Разработка под iOS
  • Swift

Узнать и овладеть ⚔️ основами RxSwift за 10 минут

Каждый разработчик слышал об Rx, будь то на последней конференции разработчиков или во время чтения публикации в блоге вроде этой ��. Практически невозможно умудриться не услышать о реактивном программировании, но что же оно всё-таки собой представляет? Заглянем в интернет:

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

Если честно, я считаю, что большинству людей, как и мне, такое объяснение не даст ни малейшего представления о том, чем, собственно, является реактивное программирование. Поэтому я попытаюсь создать своё, простое и понятное введение в этот современный подход к разработке ПО, применяя версию Rx для языка “Swift” — RxSwift.

  1. Наблюдаемые последовательности ��

Первое, что нужно понять, — всё в RxSwift является либо наблюдаемой последовательностью, либо чем-то, что оперирует распространяемыми наблюдаемой последовательностью событиями или подписывается на них.

Массивы, строки и словари в RxSwift преобразуются в наблюдаемые последовательности. Вы можете создать наблюдаемую последовательность из любого объекта, соответствующего протоколу Sequence стандартной библиотеки языка Swift.

Попробуем создать несколько наблюдаемых последовательностей:

let helloSequence = Observable.just("Hello Rx") let fibonacciSequence = Observable.from([0,1,1,2,3,5,8]) let dictSequence = Observable.from([1:"Hello",2:"World"])

Подписаться на наблюдаемые последовательности можно, вызвав

subscribe(on:(Event)-> ())

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

let helloSequence = Observable.of("Hello Rx") let subscription = helloSequence.subscribe < event in print(event) >OUTPUT: next("Hello Rx") completed

Наблюдаемые последовательности могут распространять ноль или более событий за время своего существования.

В RxSwift событие — это просто перечисление (Enumeration Type) с тремя возможными состояниями:
.next(value: T) — Когда в наблюдаемой последовательности возникает новое значение или набор значений, она отправляет событие “next” своим подписчикам, как вы могли видеть в примере выше. Ассоциированное значение будет содержать актуальное значение из последовательности.
.error(error: Error) — При обнаружении ошибки последовательность распространит событие “error”. Также это приведёт к уничтожению последовательности.
.completed — Если последовательность завершилась нормально, она отправляет подписчикам событие “completed”.

let helloSequence = Observable.from(["H","e","l","l","o"]) let subscription = helloSequence.subscribe < event in switch event < case .next(let value): print(value) case .error(let error): print(error) case .completed: print("completed") >> OUTPUT: H e l l o completed

Если вы хотите отменить подписку, вы можете сделать это, вызвав её метод dispose. Вы также можете добавить подписку в Disposebag (“корзину”), которая автоматически отменит подписку при выполнении метода deinit экземпляра DisposeBag. Кроме того, вы можете подписаться только на конкретное событие. Например, если вы хотите просто получать сообщения об ошибках, распространяемые последовательностью, вы можете использовать subscribe(onError:(Error->())).

Фрагмент кода, иллюстрирующий всё, что мы уже успели обсудить:

// Creating a DisposeBag so subscribtion will be cancelled correctly let bag = DisposeBag() // Creating an Observable Sequence that emits a String value let observable = Observable.just("Hello Rx!") // Creating a subscription just for next events let subscription = observable.subscribe (onNext:< print($0) >) // Adding the Subscription to a Dispose Bag subscription.addDisposableTo(bag)

2. Subject’ы ��

Subject — это особый вид наблюдаемой последовательности, вы можете подписаться и динамически добавлять к нему элементы. В настоящее время существует 4 различных вида Subject’ов в RxSwift

  • PublishSubject: Подписавшись на него, вы будете получать уведомления обо всех событиях, которые произойдут после вашей подписки.
  • BehaviourSubject: Предоставляет подписчику самый последний элемент и всё, что будет распространяться этой последовательностью после подписки.
  • ReplaySubject: Если вы хотите воспроизвести для новых подписчиков больше, чем только последний элемент, вам следует использовать ReplaySubject. С ReplaySubject вы можете задавать, сколько последних элементов следует распространять для новых подписчиков.
  • Variable: Это просто обёртка BehaviourSubject, легче воспринимаемая теми, кто мало знаком с реактивным программированием. Может использоваться как обычная переменная.

В рамках данной статьи я рассматриваю только работу PublishSubject. Обратитесь к дополнительному материалу на GitHub’е, если вы хотите узнать больше о других subject-типах. Они отличаются, в основном, только количеством прошлых событий, распространяемых и получаемых подписчиками по первоначальной подписке.

Publish: 0

Behaviour & Variable: 1

Replay: N

Рассмотрим PublishSubject.

Вначале нам нужно создать экземпляр PublishSubject. Делается это очень просто: можем использовать для этого инициализатор по умолчанию.

let bag = DisposeBag() var publishSubject = PublishSubject()

Мы можем добавлять новые значения к этой последовательности с помощью функции onNext(). onCompleted() завершает последовательность, а onError(error) приводит к распространению события-ошибки. Давайте добавим некоторые значения в наш PublishSubject.

publishSubject.onNext("Hello") publishSubject.onNext("World")

Если вы подпишетесь на этот subject после добавления “Hello” и “World” с помощью onNext(), вы не получите два этих значения через события. В отличие от BehaviourSubject, который бы получил “World”, поскольку это самое последнее событие.

Теперь давайте создадим подписку и добавим новые значения в Subject.

Ещё мы создадим вторую подписку и добавим ещё значений.

Обратите внимание на комментарии, чтобы понять, что именно происходит в коде.

let subscription1 = publishSubject.subscribe(onNext:< print($0) >).addDisposableTo(bag) // Subscription1 receives these 2 events, Subscription2 won't publishSubject.onNext("Hello") publishSubject.onNext("Again") // Sub2 will not get "Hello" and "Again" because it susbcribed later let subscription2 = publishSubject.subscribe(onNext:< print(#line,$0) >) publishSubject.onNext("Both Subscriptions receive this message")

Мои поздравления ��. Если вы дочитали до этого момента, значит вы ознакомились с основами RxSwift. Многое ещё предстоит изучить, но в Rx всё базируется на этих простых принципах. Можете сделать сейчас небольшой перерыв и немного поэкспериментировать с этими принципами, чтобы как следует их понять. Если вы готовы, то давайте продолжим, поскольку впереди ещё очень много интересного.

3. Marble Diagrams ����

Если вы работаете с RxSwift или вообще с Rx, вам стоит освоить Marble Diagrams (“шариковые диаграммы”). Эти диаграммы визуализируют изменения в наблюдаемых последовательностях. Диаграмма состоит из входящего потока (input stream) сверху, исходящего потока (output stream) снизу и фактической преобразующей функции посередине.

Например, давайте рассмотрим операцию, которая задерживает распространяемые события наблюдаемой последовательности на 150 миллисекунд. Не обращайте внимания на параметр “scheduler”, о нём я расскажу немного позже:

Достаточно просто для понимания, да?

Существуют замечательные open source проекты как для iOS, так и для Android, позволяющие поэкспериментировать в интерактивном режиме с такими диаграммами на ваших мобильных устройствах. Поиграйтесь с ними, и обещаю, что вы разберётесь в Rx очень быстро.

4. Преобразования ⚙️

Иногда нам может потребоваться преобразовать, объединить или отфильтровать элементы, распространяемые наблюдаемой последовательностью до того, как их получит подписчик. Я познакомлю вас с базовыми операторами преобразования, расскажу кое-что о фильтрах и о возможностях объединения последовательностей. В заключение я покажу, как производить преобразование, объединение и др. в разных потоках.

4.1 Map

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

Observable.of(1,2,3,4).map < value in return value * 10 >.subscribe(onNext:< print($0) >) OUTPUT: 10 20 30 40

4.2 FlatMap

Представим наблюдаемую последовательность, состоящую из объектов, которые сами являются наблюдаемыми, и допустим, что мы хотим создать из них новую последовательность. В таких случаях в игру вступает FlatMap. FlatMap объединит распространение итоговых наблюдаемых и распространит эти объединённые результаты как отдельную последовательность.

let sequence1 = Observable.of(1,2) let sequence2 = Observable.of(1,2) let sequenceOfSequences = Observable.of(sequence1,sequence2) sequenceOfSequences.flatMap< return $0 >.subscribe(onNext:< print($0) >) OUTPUT: 1 2 1 2

4.3 Scan

Scan принимает исходное значение и используется для агрегирования значений аналогично reduce в Swift’е.

Observable.of(1,2,3,4,5).scan(0) < seed, value in return seed + value >.subscribe(onNext:< print($0) >) OUTPUT: 1 3 6 10 15

4.4 Buffer

Оператор Buffer преобразует наблюдаемого, распространяющего элементы, в наблюдаемого, который распространяет буферизированные коллекции этих элементов.

5.Filter ��

Если на следующие события необходимо реагировать только исходя из определённого условия, используйте оператор filter.

5.1 Filter

Базовая операция фильтрации работает аналогично своему эквиваленту в Swift’е. Вы просто задаёте условие, которое должно соблюдаться, и если условие удовлетворено, то событие .next будет распространено среди его подписчиков.

Observable.of(2,30,22,5,60,1).filter 10>.subscribe(onNext:< print($0) >) OUTPUT: 30 22 60

5.2 DistinctUntilChanged

Если вы хотите распространять события next только в случае, если значение изменилось, используйте distinctUntilChanged.

Observable.of(1,2,2,1,3).distinctUntilChanged().subscribe(onNext:< print($0) >) OUTPUT: 1 2 1 3

Другие операторы фильтра, которые стоит опробовать:

6. Combine ��

Объединение последовательностей — широко распространённая задача. RxSwift предоставляет для этого множество операторов. Вот три из них:

6.1 StartWith

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

Observable.of(2,3).startWith(1).subscribe(onNext:< print($0) >) OUTPUT: 1 2 3

6.2 Merge

Существует возможность объединить выдачу от нескольких наблюдаемых с помощью оператора Merge, чтобы они вели себя как единый наблюдаемый.

let publish1 = PublishSubject() let publish2 = PublishSubject() Observable.of(publish1,publish2).merge().subscribe(onNext:< print($0) >) publish1.onNext(20) publish1.onNext(40) publish1.onNext(60) publish2.onNext(1) publish1.onNext(80) publish2.onNext(2) publish1.onNext(100) OUTPUT: 20 40 60 1 80 2 100

6.3 Zip

Используйте метод Zip, если хотите объединить распространяемые разными наблюдаемыми последовательностями элементы в одну наблюдаемую последовательность. Zip срабатывает в строгой последовательности, то есть первыми двумя элементами, распространёнными Zip’ом, будет комбинация первого элемента первой последовательности и первого элемента второй последовательности. Также имейте в виду, что Zip распространит только количество элементов, равное наименьшему числу элементов, распространяемых исходными наблюдаемыми.

let a = Observable.of(1,2,3,4,5) let b = Observable.of("a","b","c","d") Observable.zip(a,b)< return ($0,$1) >.subscribe < print($0) >OUTPUT: (1, "a")(2, "b") (3, "c") (4, "d")

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

7. Сторонние действия ��

Если вы хотите зарегистрировать callback’и, которые будут исполняться при определённых событиях в наблюдаемой последовательности, используйте оператор doOn. Он не изменяет распространяемые элементы, а просто передаёт их.

Вы можете использовать:

  • do(onNext:) — если хотите что-то делать только по событию next
  • do(onError:) — если будут распространяться ошибки и
  • do(onCompleted:) — для успешного завершения последовательности.
Observable.of(1,2,3,4,5).do(onNext: < $0 * 10 // This has no effect on the actual subscription >).subscribe(onNext:< print($0) >)

8. Schedulers

Операторы работают в том же потоке, где была создана подписка. В RxSwift используются scheduler’ы (диспетчеры), чтобы заставить операторы производить свою работу в конкретной очереди. Также вы можете заставить подписку происходить в определённой очереди. Используйте subscribeOn и observeOn для таких задач. Если вы знакомы с концепцией operation-queues или dispatch-queue, то здесь для вас не будет ничего нового. Scheduler может быть последовательным (serial) или гоночным (concurrent) аналогично GCD или OperationQueue. В RxSwift есть пять типов scheduler’ов:

  • MainScheduler — Применяется для работы, которая должна производиться в MainThread. В случае если schedule-методы вызываются из основного потока, действия будут исполнены сразу же, без диспетчеризации. Этот scheduler обычно применяется для работы с UI.
  • CurrentThreadScheduler — Распределяет порции (units) работы в текущем потоке. Это scheduler по умолчанию для операторов, генерирующих элементы.
  • SerialDispatchQueueScheduler — Применяется для работы, которая должна быть выполнена в определённом dispatch_queue_t. Проконтролирует преобразование concurrent-очереди, если таковая передана, в serial. Благодаря серийным scheduler’ам возможны определённые оптимизации observeOn. Основной scheduler является экземпляром SerialDispatchQueueScheduler.
  • ConcurrentDispatchQueueScheduler — Применяется для работы, которая должна быть выполнена в определённом dispatch_queue_t. Вы также можете передать serial dispatch очередь, это не должно вызвать какие-либо проблемы. Этот диспетчер подходит для случаев, когда некоторая работа должна выполняться в фоновом режиме.
  • OperationQueueScheduler — Применяется для работы, которая должна быть выполнена в определённом NSOperationQueue. Этот диспетчер подойдёт для случаев, когда имеется особенно большой кусок работы, которая должна быть выполнена в фоновом режиме, и вы хотите тонко настроить организацию гонки (concurrent processing), используя maxConcurrentOperationCount.

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

let publish1 = PublishSubject() let publish2 = PublishSubject() let concurrentScheduler = ConcurrentDispatchQueueScheduler(qos: .background) Observable.of(publish1,publish2) .observeOn(concurrentScheduler) .merge() .subscribeOn(MainScheduler()) .subscribe(onNext:< print($0) >) publish1.onNext(20) publish1.onNext(40) OUTPUT: 20 40

Подытожим ��

Поздравляю, вы освоили основы RxSwift. Удачного программирования! ��

В скором будущем ожидайте статью “Изучите и освоите RxCocoa”…

Добавляйте меня в github‘е, twitter‘е, linkedin‘е или xing‘е, если у вас есть какие-то вопросы. Если вам нравится электронная музыка, можете также послушать мои треки на SoundCloud 😉

ИСПОЛЬЗОВАННЫЕ ИСТОЧНИКИ:

  • https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Schedulers.md
  • http://reactivex.io/documentation/operators.html
  • http://rxmarbles.com

Автор: Sebastian Boldt
Дата публикации: 04.03.2017
Оригинал статьи
Перевод: Борис Радченко, radchenko.boris@gmail.com
Дата перевода: 23.10.2018

Rxswift что это

RxSwift является хайповой темой уже давно. Я бы даже сказал, что прошедший 2016 год был годом Rx: многие мои знакомые и коллеги так или иначе начали с ним работать или хотя бы попробовали. Чтобы самому лучше разобраться в теме и закрепить уже имеющиеся знания, я решил перевести цикл статей, где Лукаш Мроз (Łukasz Mróz) рассказывает в примерах о том, как использовать RxSwift.

Swift – язык, который приятен, что бы вы с его помощью ни делали. Он хорошо объединяет аспекты других языков, что делает Swift действительно гибким и относительно легки для понимания новичками. Поэтому его используют не только с Объектно-Ориентированным Программированием (ООП), но и с другими парадигмами, вроде новейшего Протокол-Ориентированного Программирования, представленного на WWDC 2015. Вам не нужно много искать, чтобы обнаружить, что в Swift вы можете также использовать Функциональное Программирование и Реактивное Программирование. Сегодня мы поговорим о комбинации двух последних: Функциональном Реактивном Программировании.

Итак, что такое Функциональное Реактивное Программирование? Если коротко, то это – использование Реактивного Программирование с частями Функционального Программирования (filter, map, redice и т.д.). Причём, они уже встроены в Swift! А реактивная часть обеспечивается RxSwift.

RxSwift – это версия Swift с реактивными расширениями, написанными на нём самом.

ReactiveX – это комбинация лучших идей паттерна “Наблюдатель”, “Итератор” и функционального программирования.

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

Вы можете спросить, “Почему мне бы вдруг захотелось это использовать?”. Ответ очень прост. Это делает вашу работу проще. Вместо сообщений, который сложно тестировать, мы можем использовать сигналы. Вместо делегатов, который занимают много места в коде, мы можем писать блоки и удалить многочисленные switch и if/else. У нас также есть KVO, IBActions, фильтры ввода, MVVM и много-много других вещей, которые отлично управляются RxSwift. Но помните, что это не всегда лучший способ решения проблем, но вы всё равно должны знать, когда лучше его использовать, чтобы полностью раскрыть потенциал RxSwift. Я попробую показать вам некоторые примеры, которые вы можете использовать в своём приложении.

Определения

Для начала, мы хотим начать с определений. Чтобы лучше понять логику, мы должны начать с самых основ.

Ваш смартфон – наблюдаемый (observable). Он подаёт сигналы (signals) вроде оповещений фейсбука, смс и так далее. Вы естественным образом подписаны (subscribe) на них, поэтому вы видите каждое оповещение на вашем домашнем экране. Теперь вы можете решать, что делать с этим сигналом (signal). Вы – наблюдатель (observer).

Теперь вы полностью подготовлены к примеру ниже.

Пример

Мы напишем Поисковик Городов – при печати названия города в поисковой строке он будет динамически показывать нам список. В этот момент он будет пытаться найти те города, которые начинаются с данных букв и отображать их в таблице (TableView). Довольно просто, не правда ли? Когда вы пытаетесь сделать динамический поиск в вашем приложении, всегда нужно думать о том, что может пойти не так. Например, что если я буду писать очень быстро или буду часто менять своё желание? В этом случае было бы слишком много запросов к API, которые нужно было бы фильтровать. В реальном приложении вам нужно было бы отменить предыдущий запрос, подождать какое-то время перед отправкой другого, проверить фразу на случай, если она такая же, как до этого, и так далее. Часто это приводит к огромной логике, которая выглядит довольно просто на первый взгляд. “Это всего лишь динамический поиск, что может пойти не так?”. Конечно, его можно реализовать без использования Rx, но давайте посмотрим, как мы может написать эту логику, используя совсем немного кода.

Сначала нам нужно создать новый проект. Затем установить CocoaPods и RxSwift + RxCocoa. Пример Podfile для этого представлен ниже:

Rxswift что это

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

Предпосылкой появления подобных инструментов является то, что в процессе эволюции iOS SDK представил нам широкий выбор инструментов для работы с асинхронными событиями: GCD, KVO, Notification Center, шаблон делегирования и механизм замыканий. Проблема заключается в том, что каждый из этих инструментов требует индивидуального подхода и определенной кодовой базы вокруг них, при этом их сочетание может оказаться не слишком элегантным. API, описываемый проектом ReactiveX — это попытка сделать универсальный язык для работы с асинхронными событиями. Библиотеки, реализующие этот подход, существуют для большого количества языков, так что нельзя сказать, что этот мир ограничивается только мобильной разработкой.

Базовые понятия

Глобально компоненты кода на Rx можно разделить на 3 основных вида: observables, operators и schedulers (обозреваемые последовательности, операторы и планировщики, но далее я буду пользоваться англоязычными вариантами написания).

Observables

Класс Observable является сердцем RxSwift, его назначение состоит в том, чтобы позволить одним классам подписаться на последовательности событий, содержащие данные типа T, которые транслируются другими классами.

Если заглянуть в исходники протокола ObservableType, то там можно обнаружить единственный метод subscribe , который подписывает Observer на приём событий из этой последовательности. Итак, у нас есть некий Observable, транслирующий события, есть какой-то Observer, их слушающий, а что за события такие?

События представляют собой простой enum (см. Event), и бывают трёх видов:

  • next — служит для передачи последнего пришедшего («следующего») значения Observer’у;
  • completed — говорит об успешном завершении последовательности. Это значит, что Observable успешно завершил свой жизненный цикл и больше не будет транслировать события;
  • error — говорит о том, что выполнение Observable завершилось с ошибкой и других событий транслироваться не будет.

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

Operators

Реализация класса Observable, содержит некоторое количество вспомогательных методов, которые могут производить различные операции над элементами последовательности. Так как они не имеют побочных эффектов, то их можно комбинировать вместе, что позволяет строить в декларативном стиле достаточно сложную логику. Есть сайт [RxMarbles](https://rxmarbles. com/), который визуализирует работу этих операторов, рекомендую ознакомиться.

Schedulers

Schedulers представляют собой в некотором роде аналог DispatchQueues в мире Rx. Они абстрагируют выполнение задач в различных потоках и очередях, позволяя добиться оптимальной производительности. Это не самая распространенная задача, поэтому не стоит заострять внимание на этой функциональности на первом этапе.

RxCocoa

RxSwift реализует базовое API, описанное в спецификации проекта ReactiveX. Поддержка Rx компонентами iOS SDK, реализована в модуле RxCocoa. Так мы из коробки имеем возможность подписаться на такие действия, как нажатия кнопки, переключение свитчей и т. д.

Практика

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

Установка RxSwift

Установка мало чем отличается от других библиотек. Я буду пользоваться для этой цели [CocoaPods](http://ovchinnikov. cc/tags/cocoapods/). Добaвим в Podfile следующие строки и запустим pod install :

pod 'RxSwift', '~> 4.0' pod 'RxCocoa', '~> 4.0' 

Обрабатываем нажатие кнопки в Rx

Простейший пример с обработкой нажатия кнопки в Rx

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

import RxSwift import RxCocoa 

Теперь во viewDidLoad пишем:

tapButton.rx .tap .subscribe(onNext: < [weak self] in self?.count += 1 self?.countLabel.text = "\(self?.count ?? 0)" >) .disposed(by: disposeBag) 

Разберем каждую строчку кода. Как я уже упоминал выше, RxCocoa добавляет нам семейство методов rx , которые содержат уже готовые методы, которые вернут нам ControlEvent , это тип, который удовлетворяет ObservableType , но имеет несколько специфичных особенностей (не может завершиться с ошибкой, выполняется на главном потоке и т.п.). В данном случае нас интересует метод tap (который будет соотвествовать touchUpInside ).

Далее мы подписываемся на приём событий, где в параметр onNext передаём замыкание, которое будет выполняться при приходе каждого события. В данном случае мы увеличиваем счётчик и обновляем лейбл. Так как ControlEvent представляет собой по сути бесконечную последовательность, то параметров onError и onComplete у него не будет. На самом деле, мы можем средствами RxSwift привязать и значения лейбла к значениям переменной, но об этом поговорим в будущих статьях.

Отдельно стоит упомянуть про disposeBag (aka сумка утилизации). Когда мы закончили работать с последовательностью, что у должно появится закономерное желание освободить память. Когда, мы смотрели исходники ObservableType , можно было обратить внимание, что метод subscribe возвращает объект типа Disposable . Если посмотреть его протокол, окажется, что он требует единственный метод dispose , который, как можно догадаться из названия, и служит этой цели. Но диспозить объекты вручную может быть утомительно, поэтому для этого и был придуман класс DisposeBag .

Открыв его исходник, мы видим, что в методе deinit он вызывает метод dispose , в котором далее пробегается в цикле по всем объектам, добавленным в сумку утилизации и вызывает dispose у них. Соотвествено, когда disposeBag «потеряет» ссылку на наш ViewController, т. е. её retainCount станет равным нулю, у неё будет вызван метод dealloc , который очистит все объекты, что в итоге избавляет нас от необходимости думать об утечках памяти при подписке на события.

Бонусом, чтобы до конца осознать преимущества «магии» Rx, предлагаю представить следующую гипотетическую ситуацию: к тебе приходит менеджер и говорит, что по некоторым причинам, нужно запретить пользователям нажимать на кнопку чаще чем раз в 1 секунду. Без Rx нужно будет придумывать какие-то костыли с таймером, а в нашем случае понадобится лишь воспользоваться соответствующим оператором и увеличить код всего на одну строчку:

tapButton.rx .tap .throttle(1.0, scheduler: MainScheduler.instance) .subscribe(onNext: < [weak self] in self?.count += 1 self?.countLabel.text = "\(self?.count ?? 0)" >) .disposed(by: disposeBag) 

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

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

Рекомендую почитать
  • Скрываем и показываем иконку приложения в Dock на macOS
  • Показываем индикатор загрузки файла в Finder
  • Программное добавление папки в «Избранное» в Finder
  • Управляем сторонними библиотеками с CocoaPods
  • Генерация скриншотов для AppStore при помощи Fastlane
Свежие записи
  • Переустановка старой версии macOS на Mac Mini 2011 года
  • Архитектура Redux в Swift: Введение
  • Хостинг статического контента в Firebase
  • Внешний SSD Transcend ESD240C
  • FSEvents: Разбираемся с мониторингом событий файловой системы в macOS

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

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