lynxeyedの電音鍵盤

組み込みとか電装とか

Swift PlaygroundsでのMVP実装を考えた

MVP実装考えなきゃいけないほど大規模開発しないよね

FatViewControllerガーって言うほど、iPad単体で大規模なプログラムは書かないとは思うのですが…

  1. 組み込みデバイスと通信するプログラムが多いので、ViewControllerが組み込みデバイスとの通信APIとごちゃごちゃになるのは必至
  2. 別のViewアーキテクチャの機器に移植するのが大変になる
  3. API変更があった場合の変更が辛くなる

MVCから逃げたいのはこのくらいかな。MVPにしたのはその他のアーキテクチャにくらべて学習コストが低そうと言うだけです。

MVPについてちょっとお勉強した

参考にしたサイト

そのまま、Model - View - Presenterで成り立っており、比較的それぞれが疎結合になっているアーキテクチャ
f:id:Lynx-EyED:20181018214726p:plain
Presenterの役割が重要で、

  • Viewからイベントを受け取り、Modelに処理を移譲
  • Modelから結果を受け取り、Viewに通知

したがってModel、Viewとも、互いに独立している。
今回はModelはNotificationCenterを使い、Presenter通知しています。
f:id:Lynx-EyED:20181018215939j:plain
iPad SwiftPlaygroundsですので、複数ファイルに記述するのが少々ハードルが高いので1ファイルに汚く書いてしまいました。

import PlaygroundSupport
import UIKit
import Foundation

// MVPの練習
// protocol宣言 --------------------
protocol MyViewProtocol: class {
    func reloadData()
}

protocol MyModelProtocol: MyModelProtocolNotify {
    func fetchControl()
}

protocol MyModelProtocolNotify: class {
    func addObserver(_ observer: Any, selector: Selector)
    func removeObserver(_ observer: Any)
}

protocol MyViewPresenterProtocol: class {
} 
// protocol宣言おわり---------------

class MyLiveView: UIView {
}
// -------------------------------
// View
class MyViewController: UIViewController {
    private var myTextView = UITextView(frame: CGRect(x: 20, y: 20, width: 220, height: 60))
    var presenter: MyViewPresenter!
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(myTextView)
        presenter = MyViewPresenter(view: self)
        updateControl()
        
    }
    override func loadView() {
        super.loadView()
        view = MyLiveView()
    }
    
    @objc func updateControl(){
        // Presenterに取得処理を移譲
        presenter.updateModelControl()
    }
}
extension MyViewController: MyViewProtocol {
    // Presenterから呼ばれる処理
    func reloadData() {
        myTextView.text = "通知を受信" 
    }
}
// ------------------------
// Presenter
class MyViewPresenter: MyViewPresenterProtocol {
    var view: MyViewProtocol
    var model: MyModelProtocol
    
    init(view: MyViewProtocol) {
        self.view = view
        self.model = MyModel()
        // NotificationCenter
        model.addObserver(self, selector:#selector(self.updated))
    }
    // Modelに処理を移譲
    func updateModelControl(){
        model.fetchControl()
    }
    
    // Modelから更新の通知があったらViewに更新を依頼
    @objc func updated() {
        //print("updated!!!")
        view.reloadData()
    }
}

// --------------------
// Model
class MyModel: MyModelProtocol {
    
    
    func fetchControl(){
        // NotificationCenterでPresenterに通知
        self.notify()
    }
}

extension MyModel: MyModelProtocolNotify {
    var notificationName: Notification.Name {
        return Notification.Name(rawValue: "MyModel")
    }
    
    func removeObserver(_ observer: Any) {
        NotificationCenter.default.removeObserver(observer)
    }
    
    func notify() {
        NotificationCenter.default.post(name: notificationName, object:nil)
    }
    
    func addObserver(_ obserber: Any, selector: Selector) {
        NotificationCenter.default.addObserver(obserber, selector: selector, name: notificationName, object: nil)
    }
}

PlaygroundPage.current.liveView = MyViewController()