Обложка: Зачем нужно реактивное программирование на Swift?

Зачем нужно реактивное программирование на Swift?

Это не статья-туториал по конкретной библиотеке для реактивного программирования на Swift. Вместо этого, автор хочет разобраться, почему и в каких сценариях его стоит использовать. Посмотреть примеры кода вы можете в другом нашем переводе: «Реактивное программирование на реальных примерах».

***

Улучшение читаемости кода

Все знают про кошмарные замыкания в Swift. Из-за особенностей мобильной разработки асинхронные процессы должны вызываться вовремя. Раньше это делали с помощью замыканий. Кто-то из вас может до сих пор использовать для этого замыкания и это нормально.

Но однажды вы столкнётесь с вложенными замыканиями. И начнётся кошмар. Читать и редактировать вложенные замыкания — больно. Все это понимают.

И как же реактивное программирование на Swift помогает уменьшить эту боль?

Вы можете связывать observables в цепочки с помощью функций: map, flatMap и flatMapLatest. Таким образом можно обрабатывать запросы одновременно, не утруждая себя чтением вложенных замыканий.

Также функции merge, combine и zip дают возможность выполнять несколько запросов в более управляемом и читаемом виде. Вот цитата о важности хорошей читаемости:

[quote class="icon-quote-left quote-mark"]Конечно, соотношение времени потраченного на чтение кода, ко времени потраченному на его написание — примерно 10 к 1. Мы постоянно перечитываем старый код, ведь это часть процесса написания нового. Следовательно, облегчение понимания кода также облегчает и его написание.

Роберт С. Мартин, «Чистый код: создание, анализ и рефакторинг», Должность автора

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

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

Помощь с обработкой событий

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

Для таких случаев есть несколько сценариев: делегаты, notifications, и пары ключ-значение. Каждый из них работает неплохо. Но у реактивного подхода есть, что предложить.

Во-первых, реактивное программирование может упростить работу с этими сценариями. Всё, что вам нужно, — это создать observer или subject и подписаться на него. Затем, каждый раз, когда observer порождает объект, все подписчики об этом узнают.

Можно использовать такой синтаксис:

class MyController {
  let subject:PublishSubject = 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 subject:PublishSubject { get }
}

class MyController: MyProtocol {
  let subject:PublishSubject = 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;
  • позволяет выполнять цепочки запросов парой строк кода.

Адаптированный перевод статьи «Why Swift Developers Should Be Using Reactive Programming»