Massive View Controller - 和我一起Swift

哈囉,大家好,

有一段時間不見啦,不知道大家有沒有嘗試做LookupWord來練習Swift.

今天要說的一個主題是一個iOS開發者一定會碰到的一個問題,

也是Design Pattern之中,很重要的一個。

�就是MVC - Model-View-Controller。

What the hell, 這個是什麼東西,跟我寫程式又有個什麼鬼關係?

讓我們再從溝通開始講起,

我們先有個共識,其實在寫程式當中,不過就是要溝通的內容在傳遞。就如同之前提到的開關電燈一樣,當你透過按下開關,告訴電燈你要打開或者關閉的時候,電燈會透過電線,獲得這個訊息,並且執行我們要他做的事情。

  • 按下開關
  • 電線傳遞
  • 燈泡執行

你可能會說:

實體的東西明明就是因為電路通路或者斷路才導致燈泡的明跟滅的,跟你訊息傳遞有什麼關係呀?

但是如果你想一下,如果電線牽的沒問題,沒有任何故障短路的情況下,燈泡是可以依照的你的想法去改變的,它就像是你意識的延伸,像是你額外的一個器官一樣,像是你的手,你可以控制它去執行你想做的事情(想想你怎麼控制你的手。)

這些或許都跟訊息傳遞有關,但是跟MVC - Model-View-Controller 有什麼關係?

Massive View Controller - 和我一起Swift_第1张图片
開關

Massive View Controller - 和我一起Swift_第2张图片

讓我們來進入Playground來玩一玩:

和我一起Swift吧!!


請先下載專案檔案:在這裡

這個Playground執行之後,就會看到:


Massive View Controller - 和我一起Swift_第3张图片
Playground

你可以試著切換開關,燈泡就會隨著你打開而變成黃色,關掉變回白色。

讓我們來看看程式碼是如何運作的。

你會看到一個Playground的專案,如以下的程式:

分成

  • Model

我們定義一個叫做燈的模型,他有一個狀態叫做isOn來表示這個燈有沒有被打開,而當模型創造出的實體時,一開始的狀態是false關閉的狀態。

// Model
struct Light {
    var isOn: Bool = false

    func stateDescription() -> String {

        if isOn {
            return "The light is on"
        } else {
            return "The light is off"
        }
    }
}

  • View

在iOS開發的之中,有一個基本的元件叫做UIImageViewUI是指User Interface使用者介面,Image代表他可以呈現圖片檔案,View表示,他是一個UIView,也就是要在畫面上呈現的一個基本元件。
我將它新增一個extension,多加了一個功能叫做展示燈display(light: Light),這樣當這個View就只要管他接收到了什麼樣的訊息,就呈現什麼樣貌。以這邊的例子,如果燈是亮的,就呈現Highlighted的圖片,如果不是,那就呈現一般狀態的圖片。

// View
extension UIImageView {
    func display(light: Light) {
        if light.isOn {
            self.isHighlighted = true
        } else {
            self.isHighlighted = false
        }
    }
}
  • Controller

在這邊,有一個在iOS開發之中,很重要的元件,叫做UIViewController,他為什麼重要呢,因為每一個呈現在手機上的畫面,都是一個ViewController,有一部分的開發時間,就是在處理怎麼樣讓手機從一個畫面到另一個畫面,怎麼樣讓資訊在不同的ViewController之間切換。
在這邊我們宣告了一個自己的LightViewController,他有一個變數叫做light,是一個Light的實體,也就是按著Light這個model當作藍本去做出來的一個放在記憶體裡面的物件。
而我們在這邊,有兩個View,lightImageViewswitchButton,就像上面提到的燈以及開關。什麼東西是View呢?所有使用者可以接觸到的都是View。

如果所有使用者會看到的都是View,那ViewController也是View嗎?
答: ViewController本身不是View,但如果你去看他這個Class的資料,你會發現他有一個UIView的實體叫做view 。這也是我們會在畫面上看到的真正的View,原則上Controller只有處理傳遞訊息的工作。

接著除了一些設定的程式碼之外,還有一個方法(method)叫做switchLight(_ sender:UISwitch),這個方法是使用了一個叫做"Target Action"的設計方式去傳遞訊息。在我設定switchButton的時候,我透過switchButton.addTarget(self, action: #selector(switchLight(_:)), for: .valueChanged),讓這個開關在.valueChanged值改變的時候,就會通知Controller去執行switchLight(_:)這件事情。

// Controller
class LightViewController : UIViewController {

    //Model
    var light = Light()

    // View
    lazy var lightImageView: UIImageView = {
        let imageView = UIImageView(image: #imageLiteral(resourceName: "Light_Off.png"), highlightedImage: #imageLiteral(resourceName: "Light_On.png"))
        self.view.addSubview(imageView)
        return imageView
    }()

    lazy var switchButton: UISwitch = {

        let switchButton = UISwitch()
        switchButton.addTarget(self, action: #selector(switchLight(_:)), for: .valueChanged)

        self.view.addSubview(switchButton)

        return switchButton
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = UIColor.white

    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        lightImageView.frame = CGRect(x: 100, y: 50, width: 160, height: 240)

        switchButton.frame = CGRect(x: 155, y: 310, width: 160, height: 50)
    }

    func switchLight(_ sender:UISwitch) {
        print("change to \(sender.isOn)")
        light.isOn = sender.isOn
        lightImageView.display(light: light)
    }
}

以及執行的程式碼,這段程式碼告訴Playground,我們的LightViewController,然後設定這個ViewController的內容要呈現出來。

// Run the code

let viewController = LightViewController()

PlaygroundPage.current.liveView = viewController

所以在這邊你可以看到,Model是負責去定義燈有哪些特性,View是負責任何會接觸到使用者的畫面,而Controller,他在這邊的工作最多,也是對大塊的程式碼,他需要去感知到使用者對View做了什麼事情,並且針對這個事情去做出什麼反應,在我們的例子之中,當使用者切換了開關,就會去讓我們的Model做出相對應的變化,而Model的任何變化,就會導致相對應的View,也就是燈,切換明滅。

func switchLight(_ sender:UISwitch) {
        print("change to \(sender.isOn)")
        light.isOn = sender.isOn
        lightImageView.display(light: light)
    }

在寫程式的開發的時候,儘管你有千百種方式可以完成你要的功能,但是為了考慮設計上的靈活性,我們將不同功能,不同責任的元件分開來,幫助我們思考,也幫助其他人去用人思考了解這個世界上其他東西的方式,去思考程式的運作。這個部分如果再深入的話,需要在談到OOP(Obejct-Oriented Programming 物件導向/面向對象)
但那是他日的主題,我們今天要搞懂的事MVC,所以希望你已經對他們三個是什麼玩意兒,以及負責什麼工作有一點概念,但我必須老實說,MVC的解釋也有很多種,大家也有自己的使用方式。

Massive View Controller

比較值得注意的是,你可以看到在上面的例子,Controller的部份,明顯的肥大,那是因為他除了要對事件做出相對應的邏輯判斷之外,還要處理到一些應該是View要處理的問題,例如以下的程式碼:

// View
    lazy var lightImageView: UIImageView = {
        let imageView = UIImageView(image: #imageLiteral(resourceName: "Light_Off.png"), highlightedImage: #imageLiteral(resourceName: "Light_On.png"))
        self.view.addSubview(imageView)
        return imageView
    }()

    lazy var switchButton: UISwitch = {

        let switchButton = UISwitch()
        switchButton.addTarget(self, action: #selector(switchLight(_:)), for: .valueChanged)

        self.view.addSubview(switchButton)

        return switchButton
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = UIColor.white

    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        lightImageView.frame = CGRect(x: 100, y: 50, width: 160, height: 240)

        switchButton.frame = CGRect(x: 155, y: 310, width: 160, height: 50)
    }

他敘述了那些View要呈現在哪,長什麼樣子,什麼顏色,用哪些圖片。這些本來應該是View要做的事情啊!!

所以這也是為什麼iOS的開發者會戲稱MVC為Massive View Controller,巨型的ViewController,如果你也曾經寫過任何的iOS專案,就會發現,你各種的邏輯都有可能會放在ViewController裡面,你甚至不會去想要把他的邏輯分開來,但是這樣對開發者來說,會非常花時間在review上面,也影響了測試的可能性。

延伸閱讀:

MVVM Model-View-ViewModel ,這是比較進階的主題,如果不懂也可以開發,但如果想學好的話,學習這個對於設計程式架構上會很有幫助。

// TODO: 找更好的相關資源

你可能感兴趣的:(Massive View Controller - 和我一起Swift)