Это не статья-туториал по конкретной библиотеке для реактивного программирования на Swift. Вместо этого, автор хочет разобраться, почему и в каких сценариях его стоит использовать. Посмотреть примеры кода вы можете в другом нашем переводе: «Реактивное программирование на реальных примерах».
***
Но однажды вы столкнётесь с вложенными замыканиями. И начнётся кошмар. Читать и редактировать вложенные замыкания — больно. Все это понимают.
И как же реактивное программирование на Swift помогает уменьшить эту боль?
https://tproger.ru/events/konferencija-dump2021/?utm_source=in_text
Вы можете связывать observables в цепочки с помощью функций: map, flatMap и flatMapLatest. Таким образом можно обрабатывать запросы одновременно, не утруждая себя чтением вложенных замыканий.
Также функции merge, combine и zip дают возможность выполнять несколько запросов в более управляемом и читаемом виде. Вот цитата о важности хорошей читаемости:
Это не означает, что вы должны полностью отказаться от использования замыканий. Однако, используя замыкания вместе с реактивной библиотекой по вашему выбору, вы можете сделать свой код более читаемым.
Для таких случаев есть несколько сценариев: делегаты, notifications, и пары ключ-значение. Каждый из них работает неплохо. Но у реактивного подхода есть, что предложить.
Во-первых, реактивное программирование может упростить работу с этими сценариями. Всё, что вам нужно, — это создать observer или subject и подписаться на него. Затем, каждый раз, когда observer порождает объект, все подписчики об этом узнают.
Можно использовать такой синтаксис:
class MyController {
let subjectublishSubject = PublishSubject()
func doSomething () {
let object = MyObject()
self.subject.onNext(object)
}
}
class OtherController {
func doSomething () {
let controller = MyController()
controller.subject.subscribe( (myObject) in {
// сделать что-то с myObject
})
}
}
Видите, всё достаточно просто. Для этого не нужно много кода. Как я говорил ранее, один из признаков отличной кодовой базы — она понятна любому.
Вы можете подписаться на subject из первого контроллера в любом месте программы. А затем использовать myObject для своих целей. Например, пропустить первые два объекта. Для этого можно использовать функцию skip. И как я уже говорил, вы можете комбинировать observable с помощью функций merge и zip.
Если вам нужно зацепление, можно использовать протокол и получать через него доступ к subject. Например:
protocol MyProtocol {
var subjectublishSubject { get }
}
class MyController: MyProtocol {
let subjectublishSubject = PublishSubject()
func doSomething () {
let object = MyObject()
self.subject.onNext(object)
}
}
class OtherController {
func doSomething () {
let controller = MyController()
controller.subject.subscribe( (myObject) in {
// сделать что-то с myObject
})
}
}
Допустим, у вас есть контроллеры, которым требуется observer конкретного типа. Вы можете использовать с ними любые сетевые запросы, observer’ы которых порождают объекты нужного типа.
Я всегда пользуюсь этим, когда мне требуются переиспользуемые контроллеры для отображения данных пользователю. Возьмём приложение, похожее на Instagram, где есть объект поста, и экран, где эти посты отображаются. Вам нужно выводить на одном и том же экране посты одного или другого пользователя. Так как это скорее всего делается с помощью сетевых запросов, вы можете создать запрос как observable и передать его контроллеру. Остальное контроллер сделает сам.
Это фрагмент кода иллюстрирующий мою основную идею:
class Networking {
static func getPostByUserWithId (_ userId: string) -> Observable<[Post]> {
let request = NetworkingLibrary.request(.GET, "http://someapiurl.com", parameters: nil)
.response(completionHandler: { request, response, data, error in
if let error = error {
observer.onError(error)
} else {
let posts = convertDataToPosts(data)
observer.onNext(posts)
observer.onCompleted()
}
});
}
}
class Controller {
func doSomething () {
let postsObserver = Networking.getPostByUserWithId("the_users_id")
let displayPostsController = DisplayPostsController(postsObserver)
}
}
class DisplayPostsController {
init (postsObserver: Observable<[Post]>) {
// подписка и отображение результатов запроса
}
}
Таким образом, преимущество здесь заключается в модульности и универсальности. Этот один контроллер можно использовать повторно в любое время, когда вам понадобится отобразить список сообщений. И всё, что вам нужно сделать, — это передать ему observer. Остальное сделает контроллер.
***
Кроме вышеперечисленных возможностей, реактивное программирование на Swift:
Источник статьи: https://tproger.ru/translations/zachem-nuzhno-reaktivnoe-programmirovanie-na-swift/
***
Улучшение читаемости кода
Все знают про кошмарные замыкания в Swift. Из-за особенностей мобильной разработки асинхронные процессы должны вызываться вовремя. Раньше это делали с помощью замыканий. Кто-то из вас может до сих пор использовать для этого замыкания и это нормально.Но однажды вы столкнётесь с вложенными замыканиями. И начнётся кошмар. Читать и редактировать вложенные замыкания — больно. Все это понимают.
И как же реактивное программирование на Swift помогает уменьшить эту боль?
https://tproger.ru/events/konferencija-dump2021/?utm_source=in_text
Вы можете связывать observables в цепочки с помощью функций: map, flatMap и flatMapLatest. Таким образом можно обрабатывать запросы одновременно, не утруждая себя чтением вложенных замыканий.
Также функции merge, combine и zip дают возможность выполнять несколько запросов в более управляемом и читаемом виде. Вот цитата о важности хорошей читаемости:
Поэтому очень полезно тратить время на то, чтобы сделать ваш код более понятным и читаемым.[quote class="icon-quote-left quote-mark"]Конечно, соотношение времени потраченного на чтение кода, ко времени потраченному на его написание — примерно 10 к 1. Мы постоянно перечитываем старый код, ведь это часть процесса написания нового. Следовательно, облегчение понимания кода также облегчает и его написание.
Роберт С. Мартин, «Чистый код: создание, анализ и рефакторинг», Должность автора
Это не означает, что вы должны полностью отказаться от использования замыканий. Однако, используя замыкания вместе с реактивной библиотекой по вашему выбору, вы можете сделать свой код более читаемым.
Помощь с обработкой событий
В общем сценарии iOS-разработки вам нужно реагировать на события из разных частей приложения. Например кто-то изменил своё имя в одном контроллере, и нужно обновить его в другом.Для таких случаев есть несколько сценариев: делегаты, notifications, и пары ключ-значение. Каждый из них работает неплохо. Но у реактивного подхода есть, что предложить.
Во-первых, реактивное программирование может упростить работу с этими сценариями. Всё, что вам нужно, — это создать observer или subject и подписаться на него. Затем, каждый раз, когда observer порождает объект, все подписчики об этом узнают.
Можно использовать такой синтаксис:
class MyController {
let subjectublishSubject = PublishSubject()
func doSomething () {
let object = MyObject()
self.subject.onNext(object)
}
}
class OtherController {
func doSomething () {
let controller = MyController()
controller.subject.subscribe( (myObject) in {
// сделать что-то с myObject
})
}
}
Видите, всё достаточно просто. Для этого не нужно много кода. Как я говорил ранее, один из признаков отличной кодовой базы — она понятна любому.
Вы можете подписаться на subject из первого контроллера в любом месте программы. А затем использовать myObject для своих целей. Например, пропустить первые два объекта. Для этого можно использовать функцию skip. И как я уже говорил, вы можете комбинировать observable с помощью функций merge и zip.
Если вам нужно зацепление, можно использовать протокол и получать через него доступ к subject. Например:
protocol MyProtocol {
var subjectublishSubject { get }
}
class MyController: MyProtocol {
let subjectublishSubject = PublishSubject()
func doSomething () {
let object = MyObject()
self.subject.onNext(object)
}
}
class OtherController {
func doSomething () {
let controller = MyController()
controller.subject.subscribe( (myObject) in {
// сделать что-то с myObject
})
}
}
Сделать код более модульным
Одна из моих любых особенностей реактивного программирования — передача observable в качестве переменных. Например можно передать сетевой запрос в другой объект и выполнить его, когда потребуется. Это позволяет сделать код более читаемым и модульным.Допустим, у вас есть контроллеры, которым требуется observer конкретного типа. Вы можете использовать с ними любые сетевые запросы, observer’ы которых порождают объекты нужного типа.
Я всегда пользуюсь этим, когда мне требуются переиспользуемые контроллеры для отображения данных пользователю. Возьмём приложение, похожее на Instagram, где есть объект поста, и экран, где эти посты отображаются. Вам нужно выводить на одном и том же экране посты одного или другого пользователя. Так как это скорее всего делается с помощью сетевых запросов, вы можете создать запрос как observable и передать его контроллеру. Остальное контроллер сделает сам.
Это фрагмент кода иллюстрирующий мою основную идею:
class Networking {
static func getPostByUserWithId (_ userId: string) -> Observable<[Post]> {
let request = NetworkingLibrary.request(.GET, "http://someapiurl.com", parameters: nil)
.response(completionHandler: { request, response, data, error in
if let error = error {
observer.onError(error)
} else {
let posts = convertDataToPosts(data)
observer.onNext(posts)
observer.onCompleted()
}
});
}
}
class Controller {
func doSomething () {
let postsObserver = Networking.getPostByUserWithId("the_users_id")
let displayPostsController = DisplayPostsController(postsObserver)
}
}
class DisplayPostsController {
init (postsObserver: Observable<[Post]>) {
// подписка и отображение результатов запроса
}
}
Таким образом, преимущество здесь заключается в модульности и универсальности. Этот один контроллер можно использовать повторно в любое время, когда вам понадобится отобразить список сообщений. И всё, что вам нужно сделать, — это передать ему observer. Остальное сделает контроллер.
***
Кроме вышеперечисленных возможностей, реактивное программирование на Swift:
- позволяет эффективно отлаживать код;
- сильно уменьшает количество строк кода;
- при использовании таких библиотек как RxSwift или RxCocoa, даёт возможность использовать traits для лучшего понимания кода;
- хорошо подходит для архитектуры MVVM;
- позволяет выполнять цепочки запросов парой строк кода.
Источник статьи: https://tproger.ru/translations/zachem-nuzhno-reaktivnoe-programmirovanie-na-swift/