Massive View Controller - 和我一起Swift

哈囉,大家好,

有一段時(shí)間不見(jiàn)啦,不知道大家有沒(méi)有嘗試做LookupWord來(lái)練習(xí)Swift.

今天要說(shuō)的一個(gè)主題是一個(gè)iOS開(kāi)發(fā)者一定會(huì)碰到的一個(gè)問(wèn)題,

也是Design Pattern之中,很重要的一個(gè)。

?就是MVC - Model-View-Controller。

What the hell, 這個(gè)是什麼東西,跟我寫程式又有個(gè)什麼鬼關(guān)係?

讓我們?cè)購(gòu)?a href="http://www.itdecent.cn/p/0950770caf60" target="_blank">溝通開(kāi)始講起,

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

  • 按下開(kāi)關(guān)
  • 電線傳遞
  • 燈泡執(zhí)行

你可能會(huì)說(shuō):

實(shí)體的東西明明就是因?yàn)殡娐吠坊蛘邤嗦凡艑?dǎo)致燈泡的明跟滅的,跟你訊息傳遞有什麼關(guān)係呀?

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

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

開(kāi)關(guān)

讓我們來(lái)進(jìn)入Playground來(lái)玩一玩:

和我一起Swift吧??!


請(qǐng)先下載專案檔案:在這裡

這個(gè)Playground執(zhí)行之後,就會(huì)看到:


Playground

你可以試著切換開(kāi)關(guān),燈泡就會(huì)隨著你打開(kāi)而變成黃色,關(guān)掉變回白色。

讓我們來(lái)看看程式碼是如何運(yùn)作的。

你會(huì)看到一個(gè)Playground的專案,如以下的程式:

分成

  • Model

我們定義一個(gè)叫做燈的模型,他有一個(gè)狀態(tài)叫做isOn來(lái)表示這個(gè)燈有沒(méi)有被打開(kāi),而當(dāng)模型創(chuàng)造出的實(shí)體時(shí),一開(kāi)始的狀態(tài)是false關(guān)閉的狀態(tài)。

// 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開(kāi)發(fā)的之中,有一個(gè)基本的元件叫做UIImageView,UI是指User Interface使用者介面,Image代表他可以呈現(xiàn)圖片檔案,View表示,他是一個(gè)UIView,也就是要在畫面上呈現(xiàn)的一個(gè)基本元件。
我將它新增一個(gè)extension,多加了一個(gè)功能叫做展示燈display(light: Light),這樣當(dāng)這個(gè)View就只要管他接收到了什麼樣的訊息,就呈現(xiàn)什麼樣貌。以這邊的例子,如果燈是亮的,就呈現(xiàn)Highlighted的圖片,如果不是,那就呈現(xiàn)一般狀態(tài)的圖片。

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

在這邊,有一個(gè)在iOS開(kāi)發(fā)之中,很重要的元件,叫做UIViewController,他為什麼重要呢,因?yàn)槊恳粋€(gè)呈現(xiàn)在手機(jī)上的畫面,都是一個(gè)ViewController,有一部分的開(kāi)發(fā)時(shí)間,就是在處理怎麼樣讓手機(jī)從一個(gè)畫面到另一個(gè)畫面,怎麼樣讓資訊在不同的ViewController之間切換。
在這邊我們宣告了一個(gè)自己的LightViewController,他有一個(gè)變數(shù)叫做light,是一個(gè)Light的實(shí)體,也就是按著Light這個(gè)model當(dāng)作藍(lán)本去做出來(lái)的一個(gè)放在記憶體裡面的物件。
而我們?cè)谶@邊,有兩個(gè)View,lightImageViewswitchButton,就像上面提到的燈以及開(kāi)關(guān)。什麼東西是View呢?所有使用者可以接觸到的都是View。

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

接著除了一些設(shè)定的程式碼之外,還有一個(gè)方法(method)叫做switchLight(_ sender:UISwitch),這個(gè)方法是使用了一個(gè)叫做"Target Action"的設(shè)計(jì)方式去傳遞訊息。在我設(shè)定switchButton的時(shí)候,我透過(guò)switchButton.addTarget(self, action: #selector(switchLight(_:)), for: .valueChanged),讓這個(gè)開(kāi)關(guān)在.valueChanged值改變的時(shí)候,就會(huì)通知Controller去執(zhí)行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)
    }
}

以及執(zhí)行的程式碼,這段程式碼告訴Playground,我們的LightViewController,然後設(shè)定這個(gè)ViewController的內(nèi)容要呈現(xiàn)出來(lái)。

// Run the code

let viewController = LightViewController()

PlaygroundPage.current.liveView = viewController

所以在這邊你可以看到,Model是負(fù)責(zé)去定義燈有哪些特性,View是負(fù)責(zé)任何會(huì)接觸到使用者的畫面,而Controller,他在這邊的工作最多,也是對(duì)大塊的程式碼,他需要去感知到使用者對(duì)View做了什麼事情,並且針對(duì)這個(gè)事情去做出什麼反應(yīng),在我們的例子之中,當(dāng)使用者切換了開(kāi)關(guān),就會(huì)去讓我們的Model做出相對(duì)應(yīng)的變化,而Model的任何變化,就會(huì)導(dǎo)致相對(duì)應(yīng)的View,也就是燈,切換明滅。

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

在寫程式的開(kāi)發(fā)的時(shí)候,儘管你有千百種方式可以完成你要的功能,但是為了考慮設(shè)計(jì)上的靈活性,我們將不同功能,不同責(zé)任的元件分開(kāi)來(lái),幫助我們思考,也幫助其他人去用人思考了解這個(gè)世界上其他東西的方式,去思考程式的運(yùn)作。這個(gè)部分如果再深入的話,需要在談到OOP(Obejct-Oriented Programming 物件導(dǎo)向/面向?qū)ο?
但那是他日的主題,我們今天要搞懂的事MVC,所以希望你已經(jīng)對(duì)他們?nèi)齻€(gè)是什麼玩意兒,以及負(fù)責(zé)什麼工作有一點(diǎn)概念,但我必須老實(shí)說(shuō),MVC的解釋也有很多種,大家也有自己的使用方式。

Massive View Controller

比較值得注意的是,你可以看到在上面的例子,Controller的部份,明顯的肥大,那是因?yàn)樗艘獙?duì)事件做出相對(duì)應(yīng)的邏輯判斷之外,還要處理到一些應(yīng)該是View要處理的問(wèn)題,例如以下的程式碼:

// 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要呈現(xiàn)在哪,長(zhǎng)什麼樣子,什麼顏色,用哪些圖片。這些本來(lái)應(yīng)該是View要做的事情?。。?/p>

所以這也是為什麼iOS的開(kāi)發(fā)者會(huì)戲稱MVC為Massive View Controller,巨型的ViewController,如果你也曾經(jīng)寫過(guò)任何的iOS專案,就會(huì)發(fā)現(xiàn),你各種的邏輯都有可能會(huì)放在ViewController裡面,你甚至不會(huì)去想要把他的邏輯分開(kāi)來(lái),但是這樣對(duì)開(kāi)發(fā)者來(lái)說(shuō),會(huì)非常花時(shí)間在review上面,也影響了測(cè)試的可能性。

延伸閱讀:

MVVM Model-View-ViewModel ,這是比較進(jìn)階的主題,如果不懂也可以開(kāi)發(fā),但如果想學(xué)好的話,學(xué)習(xí)這個(gè)對(duì)於設(shè)計(jì)程式架構(gòu)上會(huì)很有幫助。

// TODO: 找更好的相關(guān)資源

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容