相信大家如果讀完這篇Architecting iOS Apps with VIPER(譯),已經(jīng)對iOS的VIPER架構模式有了一定了解差凹。如果蒙蒙噠,沒關系,那么這篇文章遮婶,哥們帶你進一步認識VIPER锦援。在這篇文章中我會對公司目前項目中的VIPER架構進行分解猛蔽。同時你也可以去下載Demo。
VIPER是通過單一責任原則進行的,所以如果大家在嘗試VIPER架構模式中曼库,遇到什么問題区岗,記得上一篇文章中提到的,遵循此原則去解決問題。
保持一個類單一責任毁枯,它使類更強大慈缔。
單一責任原則規(guī)定,每個模塊或類應該對軟件提供的功能的單一部分負責种玛,并且該責任應完全由類封裝藐鹤。 它的所有服務都應該與這一責任嚴格一致。 羅伯特·馬丁表示原則如下:“A class should have only one reason to change”赂韵。
Main Parts of VIPER
The main parts of VIPER are:
View: (視圖) 顯示Presenter告知的內(nèi)容娱节,并將用戶輸入中繼回Presenter。
Interactor: (交互器)包含用例指定的業(yè)務邏輯祭示。
Presenter: (表示層肄满,也可稱主持人)包含用于準備顯示內(nèi)容(如從Interactor接收的)和用于對用戶輸入做出反應(通過從Interactor請求新數(shù)據(jù))的視圖邏輯。
Entity: (實體)包含Interactor使用的基本模型對象质涛。
Routing: (路由)包含用于描述按哪個順序顯示哪些屏幕的導航邏輯稠歉。
這種分離也符合單一責任原則。 Interactor負責業(yè)務分析師汇陆,Presenter代表交互設計師轧抗,而View負責視覺設計師。
從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
)瞬测、Router
横媚、Interactor
同時View(ViewController)
以及Interactor
的輸出
是通過Presenter
完成的
Presenter
Presenter: (表示層,也可稱主持人)包含用于準備顯示內(nèi)容(如從Interactor接收的)和用于對用戶輸入做出反應(通過從Interactor請求新數(shù)據(jù))的視圖邏輯月趟。
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
擁有View
孝宗、Router
穷躁、Interactor
, data
.
同時實現(xiàn)了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
實現(xiàn)Interactor
的接收數(shù)據(jù)輸出協(xié)議,通過此行為將自己的dataObervable
進行賦值.
presenter
實現(xiàn)view
的刷新數(shù)據(jù)輸出協(xié)議因妇,實現(xiàn)此協(xié)議的過程中調(diào)用了interactor
的提供數(shù)據(jù)的輸出行為
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
訂閱中问潭,調(diào)用View
的輸入行為. (View
的輸入?yún)f(xié)議在View(ViewControler)中實現(xiàn),刷新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主要由驅(qū)動UI的邏輯組成狡忙。 它知道何時呈現(xiàn)用戶界面。 它從用戶交互收集輸入址芯,以便它可以更新UI并將請求發(fā)送到Interactor灾茁。
Presenter從Interactor接收結果窜觉,并將結果轉(zhuǎn)換為在View中有效顯示的窗體。
Entities從不從Interactor傳遞給Presenter北专,Presenter只能準備要在View中顯示的數(shù)據(jù)禀挫。
View(ViewController)
View: (視圖) 顯示Presenter告知的內(nèi)容,并將用戶輸入中繼回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告知的內(nèi)容
HomeViewOutput將用戶輸入中繼回Presenter(loadView中調(diào)用output.viewIsReady())
Interact
它包含了操作模型對象(Entities)來執(zhí)行特定任務的業(yè)務邏輯语婴。
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)腻格。
如果你的實體只是數(shù)據(jù)結構。 任何與應用程序相關的邏輯很可能在交互器中啥繁。
Routing: (路由)
包含用于描述按哪個順序顯示哪些屏幕的導航邏輯菜职。
20170216 未完待續(xù)....