App架构:(Swift)
App设计模式:
Coordinator[kəʊ'ɔ:dɪneɪtə] 协调者
Model-View-Controller(MVC)
Model-View-ViewModel+Coordinator(MVVM-C)
Model-View-Controller+ViewState(MVC+VC)
ModelAdapter-ViewBinder(MAVB)模型适配器+视图绑定
Elm架构(The ElmArchitecture, TEA)
Github地址:https://github.com/objcio/app-architecture
UISplitViewController类时一个容器视图控制器,它显示一个master-detail界面(主界面、详情界面)。
应用架构:
App架构是软件设计的一个分支,它关心如何设计一个App的结构。
其实就是一套分类,app中不同的部件会被归纳到某个类型中去。层次:遵循一些基本规则并负责特定功能的接口和其他代码的集合。
简单设计一个功能,向文本框中输入文本,点击Commit提交,使输入文本显示在textField上,动态获取它的值
import Foundation class Model { static let textDidChange = Notification.Name("textDidChange") static let textKey = "text" var value: String { didSet { NotificationCenter.default.post(name: Model.textDidChange, object: self, userInfo: [Model.textKey: value]) } } init(value: String) { self.value = value } }
①MVC架构
必须设置初始值,然后观察他,当输入文本变化时,Model发送通知,Controller接收通知,修改View的状态数据
class MultiViewController: UIViewController, UITextFieldDelegate { let model = Model(value: "initial value") var mvcTextField: UITextField! var mvcButton: UIButton! // Strong references var mvcObserver: NSObjectProtocol? override func viewDidLoad() { super.viewDidLoad() mvcTextField.text = model.value mvcObserver = NotificationCenter.default.addObserver(forName: Model.textDidChange, object: nil, queue: nil) { [mvcTextField] (note) in mvcTextField?.text = note.userInfo?[Model.textKey] as? String } //First,set the initial value (必须设置初始值) //Then,observe it (然后观察他) //And you also have to combine with view state (并且从view到model的路径上你还必须结合view的状态数据) } func mvcButtonPressed() { model.value = mvcTextField?.text ?? "" } }
②MVP架构
避免将controller的逻辑与view的实现偶合在一起,所以你把这部分逻辑提取出来,放到另一个对象中,这个对象只通过协议和view进行通信。(跟MVC类似,只是多了一个协议作为边界)
class MultiViewController: UIViewController, UITextFieldDelegate { let model = Model(value: "initial value") mvpTextField: UITextField! var mvpButton: UIButton! var presenter: ViewPresenter? override func viewDidLoad() { super.viewDidLoad() mvpDidLoad() } } //①避免将 controller的逻辑与view的实现耦合在一起,所以你把这部分逻辑提取出来,放到另外一个对象中,这个对象只通过协议和View进行通信 //②这样如果你需要的话,可以用替换协议层来做测试 //③跟MVC类似,只是多了一个协议作为边界 protocol ViewProtocol: class { var textFieldValue: String { get set } } class ViewPresenter { let model: Model weak var view: ViewProtocol? let observer: NSObjectProtocol //观察者 //初始化方法设置model和view init(model: Model, view: ViewProtocol) { self.model = model self.view = view view.textFieldValue = model.value observer = NotificationCenter.default.addObserver(forName: Model.textDidChange, object: nil, queue: nil) { [view] (note) in view.textFieldValue = note.userInfo?[Model.textKey] as? String ?? "" } } func commit() {//提交 model.value = view?.textFieldValue ?? "" } } extension MultiViewController: ViewProtocol { func mvpDidLoad() { presenter = ViewPresenter(model: model, view: self) //简化ViewController代码 } //协议 var textFieldValue: String { get { return mvpTextField.text ?? "" } set { mvpTextField.text = newValue } } func mvpButtonPressed() { presenter?.commit() } }
③极简MVVM+不加RAC
class MultiViewController: UIViewController, UITextFieldDelegate { let model = Model(value: "initial value") var mvvmmTextField: UITextField! var mvvmmButton: UIButton! // Strong references var minimalViewModel: MinimalViewModel? var minimalObserver: NSObjectProtocol? override func viewDidLoad() { super.viewDidLoad() mvvmMinimalDidLoad() } } // Minimal MVVM class MinimalViewModel: NSObject { let model: Model var observer: NSObjectProtocol? @objc dynamic var textFieldValue: String //@objc 可以被objc观察到 (KVO动态绑定) init(model: Model) { self.model = model textFieldValue = model.value //初始阿化值 super.init() observer = NotificationCenter.default.addObserver(forName: Model.textDidChange, object: nil, queue: nil) { [weak self] (note) in self?.textFieldValue = note.userInfo?[Model.textKey] as? String ?? "" } } //我们不能从View中获取它,我们需要的时候,我们需要view传给我们 func commit(value: String) { model.value = value } } extension MultiViewController { func mvvmMinimalDidLoad() { minimalViewModel = MinimalViewModel(model: model) minimalObserver = minimalViewModel?.observe(.textFieldValue, options: [.initial, .new], changeHandler: { [weak self] (_, change) in self?.mvvmmTextField.text = change.newValue }) } func mvvmmButtonPressed() { minimalViewModel?.commit(value: mvvmmTextField.text ?? "") } }
④MVVM
class MultiViewController: UIViewController, UITextFieldDelegate { let model = Model(value: "initial value") var mvvmTextField: UITextField! var mvvmButton: UIButton! // Strong references var viewModel: ViewModel? var mvvmObserver: Cancellable? override func viewDidLoad() { super.viewDidLoad() mvvmDidLoad() } } // MVVM -加RAC(响应式编程)CwlSignal class ViewModel { let model: Model var textFieldValue: Signal<String> { return Signal .notifications(name: Model.textDidChange) //compactMap 是map操作,又会过滤nil值并解包可选值, 因为我们将必须做一串optional chaining才能获取到值 .compactMap { note in note.userInfo?[Model.textKey] as? String } //因为我们想要在信号来之前也能接收最新的值,并且我们还想要设置初始化值,所以我们要把信号的输出变成continuous的 .continuous(initialValue: model.value) //我们现在可以从model经过compactMap到continuous来遍历数据流,最后观察出这个输出; //所以数据流将持续传递给我们 } init(model: Model) { self.model = model } func commit(value: String) { model.value = value } } extension MultiViewController { func mvvmDidLoad() { viewModel = ViewModel(model: model) //数据绑定 //添加观察 subscribeValues 订阅值 mvvmObserver = viewModel!.textFieldValue .subscribeValues { [unowned self] (str) in self.mvvmTextField.text = str } } //它会追踪从model到每个暴露出来的可观察变量的路径 @IBAction func mvvmButtonPressed() { viewModel?.commit(value: self.mvvmTextField.text ?? "") } }
参考:https://www.jianshu.com/p/545f2b94ee3d
⑤MVC+VC
class MultiViewController: UIViewController, UITextFieldDelegate { let model = Model(value: "initial value") var mvcvsTextField: UITextField! var mvcvsButton: UIButton! // Strong references var viewState: ViewState? var viewStateModelObserver: NSObjectProtocol? var viewStateObserver: NSObjectProtocol? override func viewDidLoad() { super.viewDidLoad() mvcvsDidLoad() } } // MVC+VS --------------------------------------------------------- // MVC + ViewState(全局) //确保任何action所需要的状态已经从view中剥离出来,所以任何时候,我们都不依靠view来存储数据。 //所以当一个事件发生时,view回去改变viewS state 例如:commit操作,我们不依赖于view的数据, 我们不从view获取数据,而是从view state中获取它、 //对那些对于我们来说很重要的状态,我们总是能立即在代码里将它表示出来。 class ViewState {//全局共享 var textFieldValue: String = "" //观察textField的值,当它发生变化时,我们将需要把内容更新到刚才创建的view state中去 init(textFieldValue: String) { self.textFieldValue = textFieldValue } } extension MultiViewController { func mvcvsDidLoad() { viewState = ViewState(textFieldValue: model.value) mvcvsTextField.text = model.value//设置初始化值 viewStateObserver = NotificationCenter.default.addObserver(forName: .UITextFieldTextDidChange, object: mvcvsTextField, queue: nil, using: { [viewState] n in viewState?.textFieldValue = (n.object as! UITextField).text ?? "" }) viewStateModelObserver = NotificationCenter.default.addObserver(forName: Model.textDidChange, object: nil, queue: nil, using: { [mvcvsTextField] n in mvcvsTextField?.text = n.userInfo?[Model.textKey] as? String }) //更加精确地观察view,当它发生改变时,捕获这个状态 } @IBAction func mvcvsButtonPressed() { model.value = viewState?.textFieldValue ?? "" } }
⑥Elm架构
class MultiViewController: UIViewController, UITextFieldDelegate { let model = Model(value: "initial value") var driver: Driver<ElmState, ElmState.Action>? override func viewDidLoad() { super.viewDidLoad() elmViewDidLoad() } } //The Elm Architecture 不依靠storyboard来创建按钮和文本框 //如果你想和任意的view或者state打交道,你要明确创建它(需要构建其他的View,其他架构则不需要) 便于测试 //对于View,你能构建一个State然后调用来生成View 测试你是否拥有正确的View层次结构 //Elm ------------------------------------------------- struct ElmState { var text: String //虚拟构建view //模拟器中缺失的这些View,是水平方向的stackView中的 enum Action { case commit case setText(String) case modelNotification(Notification) } //给定一个action,对状态进行更新的方法 mutating func update(_ action: Action) -> Command<Action>? { switch action { case .commit: return .changeModelText(text) case .setText(let text): self.text = text return nil case .modelNotification(let note): text = note.userInfo?[Model.textKey]as? String ?? "" return nil } } var view: [ElmView<Action>] { return [ ElmView.textField(text, onChange: Action.setText),//setText将实时获得文本框的改变 ElmView.button(title: "Commit", onTap: Action.commit)//单独的按钮操作 ] } //订阅获取值 var subscriptions: [Subscription<Action>] { return [ .notification(name: Model.textDidChange, Action.modelNotification) ] } } extension MultiViewController { func elmViewDidLoad() { //驱动stackView driver = Driver.init(ElmState(text: model.value), update: { state, action in state.update(action) }, view: { $0.view }, subscriptions: { $0.subscriptions }, rootView: stackView, model: model) } }
⑦MAVB
class MultiViewController: UIViewController, UITextFieldDelegate { let model = Model(value: "initial value") var viewStateAdapter: Var<String>! override func viewDidLoad() { super.viewDidLoad() mavbDidLoad() } } // MAVB --------------------------------------------------------- extension MultiViewController { func mavbDidLoad() { viewStateAdapter = Var(model.value) TextField( .text <-- Signal .notifications(name: Model.textDidChange) .compactMap { note in note.userInfo?[Model.textKey] as? String } .startWith(model.value), .didChange --> Input().map { $0.text }.bind(to: viewStateAdapter) ).applyBindings(to: mavbTextField)//将文本描述绑定到textField上 Button( .action(.primaryActionTriggered) --> Input() //primaryActionTriggered 按钮出发一次,就会获取一次值(view state) , 更新model .trigger(viewStateAdapter) .subscribeValuesUntilEnd { [model] value in model.value = value } ).applyBindings(to: mavbButton) } }
⑧Clean Architecture
class MultiViewController: UIViewController, UITextFieldDelegate { let model = Model(value: "initial value") var cleanPresenter: CleanPresenter! override func viewDidLoad() { super.viewDidLoad() cleanDidLoad() } } // "Clean" --------------------------------------------------------- protocol CleanPresenterProtocol: class { var textFieldValue: String { get set } } class CleanUseCase { let model: Model var modelValue: String { get { return model.value } set { model.value = newValue } } weak var presenter: CleanPresenterProtocol? var observer: NSObjectProtocol? init(model: Model) { self.model = model observer = NotificationCenter.default.addObserver(forName: Model.textDidChange, object: nil, queue: nil, using: { [weak self] n in self?.presenter?.textFieldValue = n.userInfo?[Model.textKey] as? String ?? "" }) } } protocol CleanViewProtocol: class { var cleanTextFieldValue: String { get set } } class CleanPresenter: CleanPresenterProtocol { let useCase: CleanUseCase weak var view: CleanViewProtocol? { didSet { if let v = view { v.cleanTextFieldValue = textFieldValue } } } init(useCase: CleanUseCase) { self.useCase = useCase self.textFieldValue = useCase.modelValue useCase.presenter = self } var textFieldValue: String { didSet { view?.cleanTextFieldValue = textFieldValue } } func commit() { useCase.modelValue = view?.cleanTextFieldValue ?? "" } } extension MultiViewController: CleanViewProtocol { var cleanTextFieldValue: String { get { return cleanTextField.text ?? "" } set { cleanTextField.text = newValue } } func cleanDidLoad() { let useCase = CleanUseCase(model: model) cleanPresenter = CleanPresenter(useCase: useCase) cleanPresenter.view = self } @IBAction func cleanButtonPressed() { cleanPresenter.commit() } }