RxSwift.png

A beginning that never changes

I heard about ReactiveCocoa, a Github open source responsive heavyweight framework, when I was a junior in college last year. However, as I was still writing OOP at that time, I could only use the following words to describe myself.

You know nothing about power.

Later, in order to learn ReactiveCocoa, I read almost all the Materials and blogs in Chinese, and I felt a little beginner. It is true that FRP is a lot more expensive to learn than OOP, but it is definitely not as exaggerated as some people say. There may be some people who have learned some foreign technology for a long time and then smugly point at others and say, “Look! How good I am, I have learned such difficult skills!” “As if this was the only way to express their humble sense of superiority.

There is an order to hear the tao.

I’ve always believed that.

I will not discuss the superiority of FRP. Each technology stack has its own advantages and disadvantages. Learning FRP for me is more about broadening my vision and expanding my skill stack. In the early stage of the startup project, introducing a complex architecture, “advanced” framework too early, I think it will be more than worth the loss.

(2) MVVM Pattern





MVVM.png

In fact, mobile technologies have been slower than Web technologies. Web front-end event-driven and data-driven technologies have been behind for many years. For iOS platforms, RxSwift and ReSwift are relatively new frameworks, but they are Inspire By XXX without exception.

From MVC -> MVVM, every module is more pure, to the original controller filled with a variety of business code “thin”, the ViewModel should provide the View can be directly displayed or can be directly bound according to the state of the interface.

In general, the classic business scenario would look like this:





Classic business logic.jpeg

Firstly, we obtain the MetaData communicated to us by the server through the local network layer, which is generally JSON or XML. Then, we construct the corresponding Model through the transform of the client. However, the value of the Model object cannot be directly displayed to the user. So we need to Process it. For example, the UserModel gender is defined by Int 1,2, so we use our local logic code to convert it to string male and string female, and the last string data is the data exposed by the ViewModel. The View layer can then be realized through the data binding exposed by the ViewModel.

(iii) MVVM practice Demo: Gank. IO client

1. The first look

First of all, thanks to the API provided by Code home, when you want to improve yourself through actual projects, don’t hesitate to use this API to masturbate a GANK client!

The front page of this GANK client looks something like this.





Simulator Screen Shot 2.24.10.png February 24, 2017

2. About the ViewModel

What does a good ViewModel look like?

Devxoul wrote in RxTodo’s Readme about the philosophy of the ViewModel, which I was impressed by:

  • View should not have logic control flow logic, View should not cause operations on data, View can only bind data, control the display.

    Bad

    viewModel.titleLabelText .map { $0 + "!" } // Bad: The View should not have operations to modify data.Copy the code

    Good

      viewModel.titleLabelText
        .bindTo(self.titleLabel.rx.text)Copy the code
  • The View doesn’t know exactly what the ViewModel does. The View can only know from the ViewModel what it needs the View to do.

    Bad

    Viewmodel.login () // Bad: The View should not know what the viewModel doesCopy the code

    Good

      self.loginButton.rx.tap
        .bindTo(viewModel.loginButtonDidTap) 
    
      self.usernameInput.rx.controlEvent(.editingDidEndOnExit)
        .bindTo(viewModel.usernameInputDidReturn) // "Hey I tapped the return on username input"Copy the code
  • The Model should be hidden by the ViewModel, which exposes the least information the View needs to render.

    Bad

    Struct ProductViewModel {let product: Driver< product > // Bad: Model should not be exposed to View, nor need to be exposed to View.}Copy the code

    Good

      struct ProductViewModel {
        let productName: Driver<String>
        let formattedPrice: Driver<String>
        let formattedOriginalPrice: Driver<String>
        let isOriginalPriceHidden: Driver<Bool>
      }Copy the code

I fully follow these principles in practice. The main advantage is that such code structure makes App hierarchical. The practice in RxSwift looks like this:

// Tell the View the current category (iOS, Android, backend, etc...) RxDataSources let category = Variable<Int>(0) Let refreshCommand = PublishSubject<Int>() Bind let refreshTrigger = PublishSubject<Void>() // RxDataSource's core class let dataSource = RxTableViewSectionedReloadDataSource < HomeSection > () / / hidden Model fileprivate let bricks = Variable < > [Brick] ([])Copy the code

In the constructor of the ViewModel, the associated variables are initialized

override init() { section = bricks.asObservable().map({ (bricks) -> [HomeSection] in return [HomeSection(items: bricks)] }) .asDriver(onErrorJustReturn: []) super.init() refreshCommand .flatMapLatest { gankApi.request(.data(type: GankAPI.GankCategory.mapCategory(with: $0), size: 20, index: 0)) } .subscribe({ [weak self] (event) in self? .refreshTrigger.onNext() switch event { case let .next(response): do { let data = try response.mapArray(Brick.self) self? .bricks.value = data }catch { self? .bricks.value = [] } break case let .error(error): self? .refreshTrigger.onError(error); break default: break } }) .addDisposableTo(rx_disposeBag) }Copy the code

So far, the ViewModel of Gank client is well built even after the construction is completed, according to the design philosophy mentioned above.

Binding

The final step is to bind the exposed ViewModel to the View (controller or View). In the world of RxSwift, the binding is played with chained method calls. It’s worth noting that most of the binding in the “activeswift” competitor can be done with the custom <~ operator, and it seems that activeswift takes full advantage of Swift’s language features in this regard. Going back to the Gank project, the Controller level looks something like this:

do /** Rx Config */ { // Input tableView.refreshControl? .rx.controlEvent(.allEvents) .flatMap({ self.homeVM.category.asObservable() }) .bindTo(homeVM.refreshCommand) .addDisposableTo(rx_disposeBag) NotificationCenter.default.rx.notification(Notification.Name.category) .map({ (notification) -> Int in let indexPath = (notification.object as? IndexPath) ?? IndexPath(item: 0, section: 0) return indexPath.row }) .bindTo(homeVM.category) .addDisposableTo(rx_disposeBag) NotificationCenter.default.rx.notification(Notification.Name.category) .map({ (notification) -> Int in let indexPath = (notification.object as? IndexPath) ?? IndexPath(item: 0, section: 0) return indexPath.row }) .observeOn(MainScheduler.asyncInstance) .do(onNext: { (idx) in SideMenuManager.menuLeftNavigationController?.dismiss(animated: true, completion: { DispatchQueue.main.async(execute: { self.tableView.refreshControl?.beginRefreshing() }) }) }, onError: nil, onCompleted: nil, onSubscribe:nil,onDispose: nil) .bindTo(homeVM.refreshCommand) .addDisposableTo(rx_disposeBag) // Output homeVM.section .drive(tableView.rx.items(dataSource: homeVM.dataSource)) .addDisposableTo(rx_disposeBag) tableView.rx.setDelegate(self) .addDisposableTo(rx_disposeBag) homeVM.refreshTrigger .observeOn(MainScheduler.instance) .subscribe { [unowned self] (event) in self.tableView.refreshControl?.endRefreshing() switch event { case .error(_): NoticeBar(title: "Network Disconnect!" , defaultType:.error).show(duration: 2.0, completed: nil) break case.next (_): self.tableView.reloadData() break default: break } } .addDisposableTo(rx_disposeBag) // Configure homeVM.dataSource.configureCell = { dataSource, tableView, indexPath, item in let cell = tableView.dequeueReusableCell(for: indexPath, cellType: HomeTableViewCell.self) cell.gankTitle?.text = item.desc cell.gankAuthor.text = item.who cell.gankTime.text = item.publishedAt.toString(format: "YYYY/MM/DD") return cell } }Copy the code

2. Powerful RxSwift community open source components

In the RxSwift Community project list on Github, we can see a large number of RxSwift extensions and still have a large number of developers contributing to them, and we can see a vibrant Community here.

Here are some of the more commonly used extensions. If you want to learn more about them, follow the links above.

(1). Action

Anyone who has used ReactiveCocoa will be familiar with RACCommand, a component that encapsulates a user’s actions. The signal it transmits is a second-order signal, that is, the value of the transmitted signal is a signal. In the world of RxSwift, Action acts as a similar function.

(2). NSObject-Rx

Nsobject-rx, in my opinion, is the simplest and most necessary extension for RxSwift developers. For proper memory management, we always write code like this:

class MyObject: Whatever {
    let disposeBag = DisposeBag()

    ...
}Copy the code

And then repeat… Repeat… To repeat, the original USE of RxSwift so elegant FRP, is really this DisposeBag to the scenery. However, when referencing nSObject-rx, the Swift Extension method, while still displaying the DisposeBag, is much more elegant than before.

thing
  .bindTo(otherThing)
  .addDisposableTo(rx_disposeBag)Copy the code

The whole world is quiet… ——— A Chinese Odyssey

(3).RxDataSources

To be fair, RxDataSources was also standard when we were developing RxApp. If you’re using RxSwift, and you’re writing a TableView Delegate in there, then I think you should go home and raise pigs and write Swift. With RxDataSources we can perfectly bind VM and View to achieve elegant hierarchical processing and data driven. Let’s look at my example use of RxDataSources in Gank client:

Struct HomeSection {var items: [Item]} extension HomeSection: SectionModelType { typealias Item = Brick init(original: HomeSection, items: [HomeSection.Item]) { self = original self.items = items } }Copy the code

Then we need a sample driver object for DataSource

/ / / we construct HomeSection before using the let the dataSource = RxTableViewSectionedReloadDataSource < HomeSection > ()Copy the code

Finally, we just need to bind the data model in the ViewModel to the TableView or CollectionView via the RxDataSource.

    // Binding with Section Model and UITableView    
    homeVM.section
        .drive(tableView.rx.items(dataSource: homeVM.dataSource))
        .addDisposableTo(rx_disposeBag)Copy the code

Perfect, forget the massive system native Delegate…

conclusion

We can see that RxSwift has a very active and creative community that provides a set of components for elegant use of Rx, and while it takes a little bit of work to get started with RxSwift, this set of components has lowered the bar. From the Star number, it seems that the use of people is not a lot, the author here’s advice is: get on the RxSwift.

RxSwift + MVVM + Services + Routing + Moya, which is the author’s favorite architecture, this article is more about the nature of introduction, many things are skimming over, there is not too much technical content, just hope that more people understand the future, face the future, forward to the future.

This is the Demo address of this article, only for reference to Gank client.

Look at that man, he looks like a dog…

(EOF) Reference article

  1. Misunderstood MVC and deified MVVM
  2. Coordinator and RxSwift jointly build the MVVM architecture
  3. IOS Architecture Patterns – Brief description of MVC, MVP, MVVM and VIPER
  4. MVVM With ReactiveCocoa