寫在前面:這是一篇純理論的文章,深入淺出的講解一些我在學習史丹佛公開課時理解的MVC模型,它絕不是單純的翻譯其中的內容,這是筆者將他講解的內容通過自己理解過濾後一字字碼出來的,具有很強的邏輯性,以便讓大家以及我自己在今後更好的去理解
MVC全名是Model View Controller,是模型(model)-檢視(view)-控制器(controller)的縮寫,一種軟體設計典範,用一種業務邏輯、資料、介面顯示分離的方法組織程式碼,將業務邏輯聚集到一個部件裡面,在改進和個性化定製介面及使用者互動的同時,不需要重新編寫業務邏輯(來自百度百科)
簡介
MVC是一個基本模型,用於分類程式中的所有物件到三個陣營中的任意一個。
- 模型(Model)
Model = What your application is (but not how it is displayed)模型(Model),模型層是程式的行為的合集
- 檢視(View)
View = Your Controller’s minions檢視(View),檢視層是控制器(Controller)的輔助類,我們在構建我們的介面時會用到它,在檢視(View)中的內容是相當通用的介面元素
- 控制器(Controller)
Controller = How your Model is presented to the user(UIlogic)控制器(Controller),控制器(Controller)的作用給View解釋並格式化這些來自模型(Model)的資料
通訊
正確的MVC不僅僅知道內容的存放位置而且需要知道三層是如何通訊的,接下來我們總結一下這三層是如何通訊的。
(雙黃線和白色虛線表示通訊方式)
控制器與模型的通訊:
1. 控制器(Controller) -> 模型(Model):完全控制權
Controller可以瞭解Model的一切行為,並且必須完全有能力與Model通訊,根據Controller的需求使用Model公開的介面(API),因為Controller的職責就是通過View想使用者展示Model中的資料,因此Controller擁有完全控制許可權可以與Model通訊。
2. 模型(Model) -> 控制器(Controller):Notification(通知)和KVO(鍵值對觀察)
通常情況下,Model是不能與Controller通訊。
場景:Model中的資料發生了變化,而Controller需要知道這種變化,如:資料改變了,資料庫改變了。
問題:Model如何與Controller通訊告訴他這些變化?
答案:Model會用一種電臺的概念,將資訊廣播給想知道的任何Controller,在IOS中這種技術叫做Notification(通知)和KVO(鍵值對觀察),所以Model的任務就是當資料發生變化是,通過Notification(通知)和KVO(鍵值對觀察)廣播一下,隨後Controller會接收到來自廣播的資訊,他會發現資料在變化,他會再與Model通訊獲取改變後的資料
控制器與檢視的通訊
1. 控制器 -> 檢視:完全控制權
Controller是可以訪問View的,因為控制器負責通過自身物件向View傳送指令,View是Controller向使用者展示介面的一種方式,因此Controller可以控制View做任何它想做的事,這裡會提到Outlet的概念(Outlet是控制器的一個屬性,用於指向檢視)。我們建立一個Outlet實際上就是例項化一個View到Controller中,這樣我們的Controller可以傳送指令到對應View了
123 class DemoViewController: UIViewController {@IBOutlet weak var table: UITableView!}如上程式碼:UITableView作為一個檢視,table即Outlet(DemoViewController的一個指向UITableView的屬性),這樣DemoViewController就具有了對UITableView的控制權了
2. 檢視 -> 控制器:代理(Delegate)和資料來源(DataSource)
因為View是通用的,他們不能真正的瞭解呼叫它們的Controller,所以他們只能盲目的方式與Controller通訊
問題:檢視上有個按鈕(UIButton)它能跟Controller通訊嗎?
答案:可以,但是要注意,因為View是通用的,他們不能真正的瞭解呼叫它們的Controller,所以他們只能盲目的方式與Controller通訊。一種大家都認同的,View與Controller通訊的方式(Target操作和代理)方式一: addTarget操作
控制器(Controller)本身寫一個目標方法(target),然後給View的行為方法(action),並告訴View當你想做執行操作(比如你是一個按鈕被點選,或者滑動條被滑動)給控制器(Controller),你可以通過此行為方法(action)向Controller傳送訊息,通過這種方式,通用的按鈕或者拖動條就可以反過來與Controller通訊了,它根本不需要知道呼叫的它控制器到底是什麼型別的Controller,他只需要知道某一個事件在他身上觸發時他就要通過行為方法(action)想目標方法(target)傳送一個訊息,就是這樣一個盲目的、簡單的、結構化的方式來作為檢視與控制器之間的通訊。還是程式碼比較直觀`(∩_∩)′
123456789 class DemoViewController: UIViewController {@IBOutlet weak var btn: UIButton!override func viewDidLoad() {btn.addTarget(self, action: "hello:", forControlEvents: .TouchUpInside)}func hello() {print("Hello World!"}}這樣是不是很好理解:在DemoViewController中新增一個target:hello(),然後通過addTarget給View物件btn新增action。當你點選按鈕時,就是列印:Hello World!
方式二:代理(Delegate)和資料來源(DataSource)
代理(Delegate):
假如有時發生在檢視(View)上的事件是比較複雜的,理解這個的方法是我通過幾個關鍵字(will、should、did)描述這些情況:
- 假如我們的View是一個滾動檢視(ScrollView),使用者按住它,將要滑動,它想要Controller知道使用者將要執行滑動操作(will)
- 假如它正在滾動,並且它想讓Controller知道使用者剛剛做了滾動操作(did)
- 又或者使用者按住螢幕滾動檢視(ScrollView)需要知道是否允許這裡執行滾動操作(should)
所有這些情況,滾動檢視(ScrollView)本身或許沒有足夠的邏輯知道那些問題的答案,那它需要做的就是代理給Controller物件來回答這些問題(will,should,did,this,that或者其他事情例如:允許滑動、滑動到一點等等等…),你將要在那些代理協議中看到他們。這裡我們需要在控制器中代理這些協議(盲目通訊)
依舊是程式碼才會有更好的直觀性,還記得在我們文章(一)和文章(二)需要給UITableView做代理嗎?其實我介紹了兩種方式,我更偏向第二種:擴充方式,程式碼邏輯更清晰
1234567891011 extension DemoViewController: UITableViewDelegate {func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){let index = indexPath.rowlet string = self.dataList[index]if let closure = self.myClosure {closure(tag: self.myTag!,string: string)}self.dismissViewControllerAnimated(true, completion: nil)}}上述程式碼:控制器:DemoViewController代理了檢視UITableView的didSelectRowAtIndexPath協議,告訴檢視我選中了某一行時需要執行的操作。
資料來源(DataSource):
檢視(View)不應該持有展示他們的資料,如果持有資料就違背了檢視(View)的通用性了,換句話說:資料不應該作為檢視的內部屬性。
場景: 例如你要展示iPhone上的所有歌曲,你可能有10000首歌曲,另外你有一些通用的檢視列表在你的檢視(View)中,你不能傳遞這10000首歌給他例項變數中,並期望它來持有10000首歌以便你去瀏覽他們。
原因:
– 這種方式不高效;
– 這10000首歌屬於哪一層?應該是Model。
你的音樂資料庫本身就是一個Model(它與UI無關),檢視僅僅是歌曲、藝術家、專輯和其他資訊的一個列表 ,Controller必須通過Model查詢資料並告訴一個View如何展示這些歌曲,所以我們需要通訊讓View展示資料,這些通訊如何發生?答案:使用另一個種特殊的代理——資料來源(DataSource),資料來源不做之前代理(will、should、did……)的事。資料來源(DataSource)做類似:數量:(有多少首歌曲),Controller查詢Model將10000返回給檢視,然後檢視(View)為這些歌曲開闢內部空間。
當你在底部滾動它,他就開始發訊息給控制器載入10條記錄(返回150行後面的10條資料),控制器再次跟Model通訊,並獲取接下來的10條資料,於是控制器(Controller)通過這種盲目方式提供資料給檢視,這就是檢視(View)如何通過Model得到資料是通過這種不明確的、沒有針對性的、結構化的方式。
所以資料來源(DataSource)就是一個特殊型別的用於獲取資料的代理,像列表檢視(UITableView)他就有一個資料來源和一個通常的代理
依舊是程式碼:上面我們給UITableView做了代理,接下來我們為UITableView繫結資料來源
1234567891011121314 extension DemoViewController: UITableViewDataSource {func numberOfSectionsInTableView(tableView: UITableView) -> Int {return 1}func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {return self.dataList.count}func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {let cell = tableView.dequeueReusableCellWithIdentifier(self.cellId, forIndexPath: indexPath) as! DemoListCell//cell.cellImg.image = UIImage(named: powerData[indexPath.row][2])cell.cellLabel.text = self.dataList[indexPath.row]return cell}}
總結:OK!這就是View: UITableView跟控制器(Controller)的通過一種不明確的結構化方式:分為代理(Delegate)和資料來源(DataSource)
模型與檢視的通訊:不能(Never)
- 很明顯,模型(Model)是完全獨立於UI的,模型沒有任何辦法向檢視物件傳送只送指令,也不能和檢視層的任何物件通訊,因為檢視物件是基本的UI物件,他們是某種程度上是通用的,但是也是基本的UI物件,既然檢視物件是通用的物件,他們就不能和任何特定的Model物件通訊,他們需要一個控制器來傳達資訊。一句話:模型(Model)與檢視(View)之間在沒有任何通訊
重要體會:
最後你會體會到,IOS開發框架本就是MVC模式,我們常用的控制元件無一不是檢視,我們建立的ViewController其實都是控制器,我們例項化一個控制元件,就是為我們的Controller建立一個Outlet,並對其顯示和控制,說到Model,CoreData就是我們的Model,原來我們一直都在MVC的世界裡摸索到底什麼才是MVC,多麼痛的領悟!
名詞解釋:
- M(Model):模型
- V(View):檢視
- C(Controller):控制器
- Outlet :控制器指向檢視的屬性
- Notification:通知
- KVO(Key-Value Observing):鍵值對觀察
- Delegate:代理,檢視向控制器通訊,通過 will、should、did等方式統稱為代理
- DataSource:資料來源,一種特殊型別的代理,取決於檢視是否需要顯示大量資料
- protocol:協議,在程式設計中含義:我的理解通過一種盲目(Blind)的、沒有針對性的方式與其他物件通訊。
如果是一個比較大型的複雜的專案呢?在螢幕上有三四個不同的區域都在發生著什麼,我們可以結合多個MVC