iOS架构模式-VIPER

作者: HunterDude | 来源:发表于2017-02-17 17:22 被阅读5260次

    相信大家如果读完这篇Architecting iOS Apps with VIPER(译),已经对iOS的VIPER架构模式有了一定了解。如果蒙蒙哒,没关系,那么这篇文章,哥们带你进一步认识VIPER。在这篇文章中我会对公司目前项目中的VIPER架构进行分解。同时你也可以去下载Demo

    VIPER是通过单一责任原则进行的,所以如果大家在尝试VIPER架构模式中,遇到什么问题,记得上一篇文章中提到的,遵循此原则去解决问题。

    保持一个类单一责任,它使类更强大。
    单一责任原则规定,每个模块或类应该对软件提供的功能的单一部分负责,并且该责任应完全由类封装。 它的所有服务都应该与这一责任严格一致。 罗伯特·马丁表示原则如下:“A class should have only one reason to change”。

    Main Parts of VIPER

    The main parts of VIPER are:

    • View: (视图) 显示Presenter告知的内容,并将用户输入中继回Presenter。

    • Interactor: (交互器)包含用例指定的业务逻辑。

    • Presenter: (表示层,也可称主持人)包含用于准备显示内容(如从Interactor接收的)和用于对用户输入做出反应(通过从Interactor请求新数据)的视图逻辑。

    • Entity: (实体)包含Interactor使用的基本模型对象。

    • Routing: (路由)包含用于描述按哪个顺序显示哪些屏幕的导航逻辑。

    这种分离也符合单一责任原则。 Interactor负责业务分析师,Presenter代表交互设计师,而View负责视觉设计师。

    不同组件及其连接方式的图表
    项目中的Modules目录 HomeUI

    从HomeConfigurator.swift 看VIPER架构各个功能模块的交互

    class HomeModuleConfigurator {
    
        func configureModuleForViewInput<UIViewController>(viewInput: UIViewController) {
    
            if let viewController = viewInput as? HomeViewController {
                configure(viewController: viewController)
            }
        }
    
        private func configure(viewController: HomeViewController) {
    
            let presenter = HomePresenter()
            let router = HomeRouter()
            let interactor = HomeInteractor()
    
            presenter.view = viewController
            presenter.router = router
            presenter.interactor = interactor
    
            interactor.output = presenter
            viewController.output = presenter
        }
    }
    

    Presenter包含View(ViewController)、RouterInteractor

    同时View(ViewController)以及Interactor输出是通过Presenter完成的

    Presenter

    Presenter: (表示层,也可称主持人)包含用于准备显示内容(如从Interactor接收的)和用于对用户输入做出反应(通过从Interactor请求新数据)的视图逻辑。

    import RxSwift
    
    class HomePresenter {
    
        // V、I、R
        weak var view: HomeViewInput!
        var interactor: HomeInteractorInput!
        var router: HomeRouterInput!
    
        // data
        var bannerObservable: Observable<Banner>!
        var coursesObservable: Observable<Courses>!
    
        // disposebag
        let disposebag = DisposeBag()
    }
    
    
    extension HomePresenter: HomeViewOutput {
        func viewIsReady() {
            reloadData()
        }
    
        func reloadData() {
            interactor.provideBannerData(path: "app-home-carousel")
            interactor.provideWikiData(department: 2, categoryId: "54611")
    
            bannerObservable
                .flatMap {banner -> Observable<Courses> in
                    self.view.refreshBanner(banner: banner)
                    return self.coursesObservable
                }
                .subscribe(onNext: { (wiki) in
                    if let wikiResult = wiki.result {
                        if let wikiData = wikiResult.data {
                            if let wikiItem = wikiData.first {
                                self.view.refreshWiki(course: wikiItem)
                            }
                        }
                    }
                }, onError: { (error) in
                    self.view.loadDataSuccess()
                    print("onError I found \(error)!")
                }, onCompleted: {
                    self.view.loadDataSuccess()
                    print("onCompleted")
                }).addDisposableTo(disposebag)
        }
    }
    
    extension HomePresenter: HomeInteractorOutput {
        func receiveBannerData(bannerObservable: Observable<Banner>) {
            self.bannerObservable = bannerObservable
        }
    
        func receiveWikiData(coursesObservable: Observable<Courses>) {
            self.coursesObservable = coursesObservable
        }
    }
    

    presenter 拥有ViewRouterInteractor, data.

    同时实现了HomeViewOutput以及 HomeInteractorOutput.

    HomeViewOutput.swift

    protocol HomeViewOutput {
    
        /**
            @author xijinfa
            Notify presenter that view is ready
        */
    
        func viewIsReady()
    
        func reloadData()
    }
    

    HomeInteractorOutput.swift

    import Foundation
    import RxSwift
    
    protocol HomeInteractorOutput: class {
            func receiveBannerData(bannerObservable: Observable<Banner>)
            func receiveWikiData(coursesObservable: Observable<Courses>)
    }
    

    presenter实现Interactor的接收数据输出协议,通过此行为将自己的dataObervable进行赋值.

    presenter实现view的刷新数据输出协议,实现此协议的过程中调用了interactor的提供数据的输出行为

    interactor.provideBannerData(path: "app-home-carousel")
    interactor.provideWikiData(department: 2, categoryId: "54611")
    

    HomeInteractorInput.swift

    protocol HomeInteractorInput {
        func provideBannerData(path: String)
        func provideWikiData(department: Int, categoryId: String)
    }
    

    在对dataObservable订阅中,调用View的输入行为. (View的输入协议在View(ViewControler)中实现,刷新UI。)

    self.view.refreshBanner(banner: banner)
    self.view.refreshWiki(course: wikiItem)
    self.view.loadDataSuccess()
    

    HomeViewInput.swift

    protocol HomeViewInput: class {
    
        /**
            @author xijinfa
            Setup initial state of the view
        */
    
        func setupInitialState()
    
        func refreshBanner(banner: Banner)
    
        func refreshWiki(course: CourseData)
    
        func loadDataSuccess()
    }
    

    Presenter主要由驱动UI的逻辑组成。 它知道何时呈现用户界面。 它从用户交互收集输入,以便它可以更新UI并将请求发送到Interactor。

    Presenter从Interactor接收结果,并将结果转换为在View中有效显示的窗体。

    Entities从不从Interactor传递给Presenter,Presenter只能准备要在View中显示的数据。

    View(ViewController)

    View: (视图) 显示Presenter告知的内容,并将用户输入中继回Presenter。

    //
    //  HomeHomeViewController.swift
    //  xjf-ios-mvvm
    //
    //  Created by xijinfa on 18/01/2017.
    //  Copyright © 2017 xijinfa. All rights reserved.
    //
    
    import UIKit
    import PullToRefresh
    
    final class HomeViewController: UIViewController {
    
        // MARK: Properties
    
        var output: HomeViewOutput!
    
        private let refresher = PullToRefresh()
    
        fileprivate lazy var carsouselView: CarouselViewController = {
            return CarouselViewController(path: "app-dept3-carousel")
        }()
    
        fileprivate lazy var wikiCardView: WikiCardView = {
            return WikiCardView()
        }()
    
        fileprivate lazy var scrollView: UIScrollView = {
            return UIScrollView()
        }()
    
    
        // MARK: Life cycle
    
        override func loadView() {
            super.loadView()
    
            view.backgroundColor = UIColor.HexRGB(rgbValue: 0xf5f5f5)
    
            func addSubviews() {
                view.addSubview(scrollView)
                scrollView.addSubview(carsouselView.view)
                scrollView.addSubview(wikiCardView)
            }
    
            func configViews() {
                let homeConfigurator = HomeModuleConfigurator()
                homeConfigurator.configureModuleForViewInput(viewInput: self)
    
                let carsouselConfigurator = CarouselModuleConfigurator()
                carsouselConfigurator.configureModuleForViewInput(viewInput: carsouselView)
            }
    
            func layoutSubViews() {
                let screenWidth = UIScreen.main.bounds.width
                let screenHeight = UIScreen.main.bounds.height
                let statusBarHeight = UIApplication.shared.statusBarFrame.height
    
                scrollView.frame = CGRect(x: 0, y: statusBarHeight, width: screenWidth, height: screenHeight)
                scrollView.contentSize = CGSize(width: 0, height: screenHeight + 1)
    
                carsouselView.view.snp.makeConstraints { make in
                    make.width.equalTo(scrollView.snp.width)
                    make.height.equalTo(160)
                    make.top.equalTo(scrollView)
                }
    
                wikiCardView.snp.makeConstraints { make in
                    make.width.equalTo(scrollView.snp.width)
                    make.height.equalTo(333)
                    make.top.equalTo(carsouselView.view.snp.bottom).offset(10)
                }
            }
    
            func setupPullToRefresh() {
                scrollView.addPullToRefresh(refresher) { [weak self] in
                    print("PullToRefresh")
                    func reloadData() {
                        self?.output.reloadData()
                    }
                    reloadData()
                }
            }
    
            addSubviews()
    
            layoutSubViews()
    
            configViews()
    
            setupPullToRefresh()
    
            output.viewIsReady()
        }
    
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            func initNaviBar() {
                if let naviVC = self.navigationController {
                    naviVC.setNavigationBarHidden(true, animated: false)
                }
            }
            initNaviBar()
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
        }
    
        deinit {
            if let topPullToRefresh = scrollView.topPullToRefresh {
                scrollView.removePullToRefresh(topPullToRefresh)
            }
        }
    }
    
    extension HomeViewController: HomeViewInput {
        func setupInitialState() {
    
        }
    
        func refreshBanner(banner: Banner) {
            Logger.logInfo(message: "refresh banner")
            carsouselView.setBanner(banner: banner)
        }
    
        func refreshWiki(course: CourseData) {
            Logger.logInfo(message: "refresh wiki")
            wikiCardView.setData(courseData: course)
        }
    
        func loadDataSuccess() {
            Logger.logInfo(message: "load data success")
            scrollView.endRefreshing(at: Position.top)
        }
    }
    
    

    HomeViewInput显示Presenter告知的内容

    HomeViewOutput将用户输入中继回Presenter(loadView中调用output.viewIsReady())

    Interact

    它包含了操作模型对象(Entities)来执行特定任务的业务逻辑。

    class HomeInteractor {
        weak var output: HomeInteractorOutput!
    }
    
    extension HomeInteractor: HomeInteractorInput {
        func provideBannerData(path: String) {
            self.output.receiveBannerData(bannerObservable: DataManager.getBanner(path: path))
        }
    
        func provideWikiData(department: Int, categoryId: String) {
            var params = Dictionary<String, String>()
            params.updateValue(categoryId, forKey:"category_id")
            self.output.receiveWikiData(coursesObservable: DataManager.getCourses(department: department, params: params))
        }
    }
    

    Entity (实体)

    实体是由交互器操作的模型对象。 实体仅由交互器操纵。 交互器从不将实体传递到表示层(即Presenter)。
    如果你的实体只是数据结构。 任何与应用程序相关的逻辑很可能在交互器中。

    Routing: (路由)

    包含用于描述按哪个顺序显示哪些屏幕的导航逻辑。


    20170216 未完待续....

    相关文章

      网友评论

        本文标题:iOS架构模式-VIPER

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