美文网首页
A taste of MVVM and Reactive par

A taste of MVVM and Reactive par

作者: 长鲜 | 来源:发表于2019-02-12 13:51 被阅读0次

    A taste of MVVM and Reactive paradigm

    一次Reactive和MVVM的编程范式尝试

    原文地址

    我喜欢Swift, 就像其他许多面向对象的编程语言一样,Swift会允许你去用各种特征或者行为来模拟真实世界。

    我认为一个App就是一个世界,而其中的每一个对象就是一个人。他们工作,交流。如果某个人不想孤单工作,那么他就需要去寻求帮助。就拿一个项目来说,打个比方,如果管理者要把所有的工作包揽,那么他是活得不耐烦了。所以我们需要去组织和分配工作。这需要许多人在项目之中合作分工: 比如设计师,测试,经验丰富的工程师和普通开发者。当其他的任务被完成的时候,管理者必须被告知任务被完成了。

    这可能不是一个好的例子。不过至少,这样你能明白了交流和代理在OOP中的重要性。从我开始iOS编程的时候,我对 “架构” 这个词非常好奇。 但在我工作了一段时间之后,这一切都变成了识别和分配能力。这篇文章希望能告知一些关于MVC的知识,并且通过一些额外的Class将其改造成MVVM, 并且还会同时介绍如何走向Rx。你想怎么建造你的架构是你的自由,但是无论如何,为了不要误导或者吓到他们,你最好能保持编程的一致性。

    Model View Controller

    看一下你所知道的最优秀的架构, MVC。View是你展示Data的地方,这里会包含UIView UIButton UILabel之类的控件。Model 就是你的Data, 他可以是来自网络或者数据库或者缓存中的的data. ViewController就是其中的协调者。

    [站外图片上传中...(image-866995-1549950646350)]

    UIViewController是一切的中心

    ViewController最大的问题就是,他容易变得臃肿。Apple把他置于一个非常中心的位置。这意味着他会又许多属性和责任。 当然有一些事情你是只能通过UIViewController来处理的,比如和Storyboard 交互,管理View, 管理旋转事件。UIViewController就是设计为有许多功能让你去重写的。

    稍微过一下UIViewController的文档,你就会发现,以下事情是你必须要通过UIViewController完成的

    func viewDidLoad()
    var preferredStatusBarStyle: UIStatusBarStyle { get }
    UITableViewDataSource
    var presentationController: UIPresentationController? { get }
    func childViewControllerForScreenEdgesDeferringSystemGestures() -> UIViewController?
    func didMove(toParentViewController parent: UIViewController?)
    var systemMinimumLayoutMargins: NSDirectionalEdgeInsets
    var edgesForExtendedLayout: UIRectEdge
    var previewActionItems: [UIPreviewActionItem]
    var navigationItem: UINavigationItem
    var shouldAutorotate: Bool
    

    随着App的壮大,我们需要去增加许多的逻辑代码。 比如网络,数据源,处理多方的代理,展示子Controlelr. 我们当然可以把这些事件直接让UIViewController直接去做,不过这样会让他变得非常的臃肿。并且会非常锻炼你滚动屏幕的能力。把所有的事情都交给UIViewController来做会让你失去宏观上分配指责的能力。如此一来,你会更倾向复制代码,并且bug会不易被修复,因为他们都堆积在一个地方.

    架构界的流行语

    当你的ViewController变得庞大了,你会怎么做?有人会把网络的工作挪到其他组件。顺便说一下,如果你希望有其他的对象来处理用户的手动输入,你可以用Presenter.如果Presenter也做了太多事情,那么有人会把业务逻辑搬到Interactor.同时,以下还有许多流行语供你选择

    let buzzWords = [
      "Model", "View", "Controller", "Entity", "Router", "Clean", "Reactive", 
      "Presenter", "Interactor", "Megatron", "Coordinator", "Flow", "Manager"
    ]
    let architecture = buzzWords.shuffled().takeRandom()
    let acronym = architecture.makeAcronym()
    

    [站外图片上传中...(image-1b9e95-1549950646350)]

    实用主义程序员

    人们对于什么是好的架构总是有着不同的理解。对于我而言,这个问题是

    明确的指责分离,好的交互模式,易用。

    架构中的每一个组件都应该可以被确认,并且有特定的功能。交互必须要清晰以便我们了解对象之间是怎么协调的。这些东西利用依赖注入在一起之后会让测试变得更加容易。

    理论上听起来不错的事情,在实际中并不一定好用。分离指责听起来很酷,协议扩展好想也不错,多层抽象也很牛皮,不过这之中有太多的问题了。

    如果你已经阅读过足够多的设计模式,那么你一定知道他们无非都是在讲这些事:

    • 差异点的封装:把你的应用里的差异点封装成一个同样的问题。
    • 用接口来编程,而不是靠实现来编程(这句话不错)
    • 合成比继承好

    如果说有一件事我们必须精通,那么就是合成了。这是指责分离并且合并起来的关键点。和你的同事的咨询,并讨论出一个适合的模式。总是用一种你会是今后维护者的想法来编程,这样的代码写出来可能会稍有不同。

    不要跟系统做对

    有一些架构会做一些新的编程范例。有一些人会写脚本去生成代码,造成代码很笨重。对于一个问题的处理有许多方式,不过对于我来说,这个感觉就像是在跟系统做对。我们不能仅仅因为一个架构时髦就去把自己困在里面,务实是最重要的。

    在iOS的世界里,我们应该拥抱MVC. UIViewController不是屏幕内容。他可以被其他包含,也可以由其他组合,以此来实现分离指责的功能。我们可以用Coordinator或者FlowController来管理依赖或者控制流(Flow). 状态转换的容器,嵌入式逻辑控制器,屏幕内容的一部分。这种拥抱ViewController的方法可以很好地与iOS中的 MVC配合,也是我最喜欢的方式。

    Model View ViewModel

    [站外图片上传中...(image-18a8ab-1549950646350)]

    至于另外一种把任务卸载给其他对象的方法,叫ViewModel.

    这个名字并不重要,你可以叫他Reactor, Maestro, Dinosaur. 重要的是你的团队有一个一致的名字,ViewModel会接管部分来自ViewController的任务,并且在完成之后回馈给ViewController.Cocoa Touch里有许多交流模式,比如Delegate(代理), closure(闭包)

    ViewModel是自立门户的,对于UIKit没有依赖,并且只有input和output. 我们可以把许多事情交给ViewModel去做,比如计算,格式化,网络,业务逻辑。当然,如果你不希望你的ViewModel变得太臃肿,你还需要去创建一些专用的对象。ViewModel只是你去创建一个Slim ViewController的第一步。

    同步(Synchronously)

    下面是一个非常简单的根据user Model 格式化数据的ViewModel, 他做到了同步 .

    class ProfileController: UIViewController {
      override func viewDidLoad() {
        super.viewDidLoad()
        let viewModel = ViewModel(user: user)
        nameLabel.text = viewModel.name
        birthdayLabel.text = viewModel.birthdayString
        salaryLabel.text = viewModel.salary
        piLabel.text = viewModel.millionthDigitOfPi
      }
    }
    

    异步(Asynchronously)

    我们总是会和异步API打交道。比如我们想要去展示一下我们的Facebook好友。为此,我们肯定是要去调用一下Facebook的API.这个API是不会立刻返回的,下面的这个ViewModel给我们展示里用闭包回调的方法。

    viewModel.getFacebookFriends { friends in
      self.friendCountLabel.text = "\(friends.count)"
    }
    

    在Viewmodel里,这个任务会被下发给专用的FacebookAPI对象

    class ViewModel {
      func getFacebookFriends(completion: [User] -> Void) {
        let client = APIClient()
        client.getFacebookFriends(for: user) { friends in
          DispatchQueue.main.async {
            completion(friends)
          }
        }
      }
    }
    

    绑定(Binding)

    为了封装闭包,我们可以创建一个可以通知多个监听者的叫Binding的类。这个功能主要是靠didSet的实现的。

    class Binding<T> {
      var value: T {
        didSet {
          listener?(value)
        }
      }
      private var listener: ((T) -> Void)?
      init(value: T) {
        self.value = value
      }
      func bind(_ closure: @escaping (T) -> Void) {
        closure(value)
        listener = closure
      }
    }
    

    在Viewmodel中,我们这样使用.

    class ViewModel {
      let friends = Binding<[User]>(value: [])
      init() {
        getFacebookFriends {
          friends.value = $0
        }
      }
      func getFacebookFriends(completion: ([User]) -> Void) {
        // Do the work
      }
    }
    

    当friends被获取,或者变更的时候。ViewController会同时更新,这就是reaction.

    override func viewDidLoad() {
      super.viewDidLoad()
      viewModel.friends.bind { friends in
        self.friendsCountLabel.text = "\(friends.count)"
      }
    }
    

    我们通常会在MVVM的简介里面看到reactive框架,这是有道理的。这个框架提供非常多的链式操作符,让编程变得更加简单和陈述性。

    RxSwift

    在Swift中最常见的reactive框架就是RxSwift了。和RxJava,RxJs,RxKotlin都很相似。

    [站外图片上传中...(image-9264e2-1549950646350)]

    RxSwift通过Obserable统一了同步和异步操作。 下面是你如何创建的示范

    class ViewModel {
      let friends: Observable<[User]>
      init() {
        let client = APIClient()
        friends = Observable<[User]>.create({ subscriber in
          client.getFacebookFriends(completion: { friends in
            subscriber.onNext(friends)
            subscriber.onCompleted()
          })
          return Disposables.create()
        })
      }
    }
    

    Rxswift的强大在于他众多的运算符。它可以帮助我们连接Observable, 在这里你可以创建两个网络请求,等到他们一起结束,之后把结果合并。这个操作非常流线型,并且节约时间。在这里,你只要订阅Observable, 它就会在请求结束的时候被触发。

    override func viewDidLoad() {
      super.viewDidLoad()
      viewModel.friends.subscribe(onNext: { friends in
        self.friendsCountLabel.text = "\(friends.count)"
      })
    }
    

    Input 和 output

    Rxswift提供了非常简洁的接口,让我们可以通过Obserable分离input和output.

    下面的fetch是一个input, friends是一个可以获取的output.

    class ViewModel {
      class Input {
        let fetch = PublishSubject<()>()
      }
      class Output {
        let friends: Driver<[User]>
      }
      let apiClient: APIClient
      let input: Input
      let output: Output
      init(apiClient: APIClient) {
        self.apiClient = apiClient
        // Connect input and output
      }
    }
    
    class ProfileViewController: BaseViewController<ProfileView> {
      let viewModel: ProfileViewModelType
      init(viewModel: ProfileViewModelType) {
        self.viewModel = viewModel
      }
      override func viewDidLoad() {
        super.viewDidLoad()
        // Input
        viewModel.input.fetch.onNext(())
        // Output
        viewModel.output.friends.subscribe(onNext: { friends in
          self.friendsCountLabel.text = "\(friends.count)"
        })
      }
    }
    

    reactive是如何工作的

    如果你喜欢Rx, 那么在使用一段时间之后再去了解他们是非常有必要的。这里面有许多概念,比如Signal, SignalProducer, Observable, Promise, Future, Task, Job, Launcher, Async.

    Monad(单子)

    Signal和它的Result都是monads,可以被map和chain.

    Signal使用延迟执行的闭包。他可以被push或者pull来更新值和执行顺序.

    延迟执行意味着我们传输一个会在将来的某个事件被执行的function .

    同步VS异步

    Monad可以处于同步或者异步模式。

    总的来说:

    • 同步: 立刻得到返回值
    • 异步: 通过回调得到返回值

    下面是一个同步异步的方法

        // Sync
    func sum(a: Int, b: Int) -> Int {
        return a + b
    }
    
    // Async
    func sum(a: Int, b: Int, completion: Int -> Void) {
        // Assumed it is a very long task to get the result
        let result = a + b
        completion(result)
    }
    

    那么同步和异步是如何都被当作是Result类型呢? 注意在使用异步的时候,我们在闭包里得到一个计算后的值,而不是立刻返回。

    enum Result<T> {
      case value(value: T)
      case failure(error: Error)
    
      // Sync
      public func map<U>(f: (T) -> U) -> Result<U> {
        switch self {
        case let .value(value):
          return .value(value: f(value))
        case let .failure(error):
          return .failure(error: error)
        }
      }
    
      // Async
      public func map<U>(f: @escaping ((T), (U) -> Void) -> Void) -> (((Result<U>) -> Void) -> Void) {
        return { g in   // g: Result<U> -> Void
          switch self {
          case let .value(value):
            f(value) { transformedValue in  // transformedValue: U
              g(.value(value: transformedValue))
            }
          case let .failure(error):
            g(.failure(error: error))
          }
        }
      }
    }
    

    Push Signal

    给出这样的链式signals

    A -(map)-> B -(flatMap)-> C -(flatMap)-> D -(subscribe)
    

    Push signal意味着当signal A被发送的时候,他通过回调传播事件。PushSignal相当于在RxSwift里的PublishSubject

    • 通过发送事件被触发。
    • 通过持有A来保持其他。
    • 订阅最后一个事件D
    • 发送第一个事件A
    • 当A的回调结束时,同时会执行B的回调,并一直传递下去.

    下面是一个Swift4版本的PushSignal实现

    public final class PushSignal<T> {
      var event: Result<T>?
      var callbacks: [(Result<T>) -> Void] = []
      let lockQueue = DispatchQueue(label: "Serial Queue")
    
      func notify() {
        guard let event = event else {
          return
        }
    
        callbacks.forEach { callback in
          callback(event)
        }
      }
    
      func update(event: Result<T>) {
        lockQueue.sync {
          self.event = event
        }
    
        notify()
      }
    
      public func subscribe(f: @escaping (Result<T>) -> Void) -> Signal<T> {
        // Callback
        if let event = event {
          f(event)
        }
    
        callbacks.append(f)
    
        return self
      }
    
      public func map<U>(f: @escaping (T) -> U) -> Signal<U> {
        let signal = Signal<U>()
    
        _ = subscribe { event in
          signal.update(event: event.map(f: f))
        }
    
        return signal
      }
    }
    

    下面是一个PushSignal使用作计算字符串长度

    let signal = PushSignal<String>()
    _ = signal.map { value in
      return value.count
    }.subscribe { event in
      if case let .value(value) = event {
        print(value)
      } else {
        print("error")
      }
    }
    signal.update(event: .value(value: "test"))
    

    Pull Signal

    给出一个如下的链式signal

    A -(map)-> B -(flatMap)-> C -(flatMap)-> D -(subscribe)
    

    Pull Signal, 有时候被叫做Future. 他会在你订阅D的时候,调用之前的Signal

    • 由订阅D触发。
    • 持有D, 因为D持有其他对象。
    • 必须去订阅最后一个D
    • D的操作引发C的动作。。 最终引发A的动作。

    下面是Swift4版本的Pullsignal. PullSignal类似于RxSwift里的Observable. 或者ReactiveSwift里的SignalProducer.

    public struct PullSignal<T> {
      let operation: ((Result<T>) -> Void) -> Void
      public init(operation: @escaping ((Result<T>) -> Void) -> Void) {
        self.operation = operation
      }
      public func start(completion: (Result<T>) -> Void) {
        operation() { event in
          completion(event)
        }
      }
      public func map<U>(f: @escaping (T) -> U) -> PullSignal<U> {
        return PullSignal<U> { completion in
          self.start { event in
            completion(event.map(f: f))
          }
        }
      }
    }
    

    这条链式会当你在链条的末端start时运行。下面还是一段测试代码

    let signal = PullSignal<String> { completion in
      // There should be some long running operation here
      completion(Result.value(value: "test"))
    }
    signal.map { value in
      value.count
    }.start { event in
      if case let .value(value) = event {
        print(value)
      } else {
        print("error")
      }
    }
    

    我希望这些代码片段可以帮助你理解signal工作,理解冷信号和热信号。如果想完全理解signal. 你需要去实现更多的操作符,比如retry, rebounce, throttle, queue, faltten, filter, delay, combine. 并且支持对UIKit的操作,比如RxCocoa. 也可以在我的仓库里查看他们的实现。

    下一步怎么走?

    架构是一个非常个性化的话题。希望本文可以给你一些帮助。MVC是iOS里的有着强大统治力的,MVVM则是一个好朋友,而Rx是一个强大的工具。下面是一些有趣的文章:

    相关文章

      网友评论

          本文标题:A taste of MVVM and Reactive par

          本文链接:https://www.haomeiwen.com/subject/gkxkeqtx.html