上一話中介紹了擴充和協定的相關知識,這一話我們介紹一個很重要的概念delegation(代理),代理是協定的一個很重要的應用。我們來回顧一下代理的相關知識,它是控制器和試圖通訊的關鍵。
那麼代理是如何工作的呢

1.你需要建立一個代理協定,它描述了這個試圖要幫别人做的事情。
2.在你的視圖中建立一個屬性,稱作代理或者有時也叫資料源,這個屬性的類型就是你建立的代理協定。
3.然後你使用這個屬性去處理所有的代理,代理屬性會去請求它所需要的資料,記得試圖本身是不能擁有資料的。隻要是遵循這個協定的對象都可以向這個協定中的屬性設值,有了值之後我們的視圖就知道該如何做了。
4.控制器首先會說它自身實作了這個協定,然後它将它自身作為代理對象
5.控制器實作所有的代理方法,這樣它就遵循了協定
現在控制器已經和視圖建立了連接配接,雖然它不知道控制它的類是什麼樣的,這些資料是什麼,它唯一知道的是這個控制器實作了視圖中的協定方法。
下面讓我們回到我們的小人臉的Demo中看看如何實作上述的操作。
讓faceView中的代理獲得它的資料,這個資料就是它的smiliness,而HappinessViewController會成為它的控制器。
在FaceView中加入如下代碼:
可能通常我們的命名應該是FaceViewDelegate,但是這個協定的作用主要是傳回smiliness的值,這種代理叫DataSource,是以我們這麼命名。傳入的參數類型是FaceView,即将自己傳入,在IOS中如果某個對象擁有一個Delegate或者DataSource的時候,它們都會傳入它們自身。
第二步是,在我們的FaceView中我們需要有一個公開的變量:
可以看到這裡把我們的協定作為了資料類型,那麼如果我們想要讓某個協定成為我們的代理,隻需要将協定設定為類的變量的值,使用可選型是因為沒有控制器去遵循我們的代理協定,那麼我們的小人臉的嘴就會是一條直線不會變化,協定可以是nil,雖然通常我們不希望它是nil。
我們希望它是弱類型的:
這涉及到記憶體管理的知識,事實上記憶體是被作業系統自動管理的,是以不需要擔心。但是需要注意的是如果控制器把它自身當做代理,然後它把指針指向自身,不幸的是如果我們的控制器在視圖層上已經有一個FaceView的指針了,因為它有一個連接配接到FaceView的outlet,這樣兩個對象就互相指向了對方。這種情況下它們就會一直在記憶體中互相引用對方,記憶體中出現這樣的循環是很可怕的,因為控制器和視圖都無法釋放記憶體,它們會永遠呆在記憶體中,是以使用weak關鍵字意思是無論它指向了什麼對象,它都不該留在記憶體中。通常我們不怎麼使用weak,但是在IBOutlet中weak被大量使用,代理的使用是另一個應用到weak的地方。這樣我們有一個代理的時候我們始終需要一個weak類型的變量。
你會發現上面這句話報錯了,提示我們weak隻能用在類中,雖然我們的代理是一個協定,但是前幾話中講到了你可以通過如下寫法把它設定為隻能被類遵循:
現在這個代理将不能被枚舉和結構體實作了。
之前smiliness的值是寫死的,現在我們希望smiliness的值由代理提供,做如下修改:
你會看到下面這句報錯了:
這是因為現在我們的smiliness是一個可選值,而bezierPathForSmile的參數必須不為空,那麼我們該怎麼辦呢,我們又要接觸到Swift中一個很酷的特性,那就是??操作符了。
??操作符的意思是如果左邊的表達式為nil那麼就使用??右邊的值來傳回,如果左邊的值不為nil就傳回左邊的值,現在如果代理傳入的值為空的話,那麼smiliness會得到0.0的值,小人臉的嘴是一條直線。
現在讓我們來到實作代理的另一邊,也就是HappinessViewController。
首先讓它遵守協定:
一旦你這樣寫就會收到一個報錯提示你沒有實作代理方法。
隻需要輸入代理方法的前幾個字母系統會自動關聯。代理方法不知道happiness是做什麼的,因為它是模型層,控制器的任務就是為視圖解析這個模型,你也會為了模型去解析視圖,這個在手勢識别的時候會講到。
那麼代理方法中的内容:
這樣我們把模型中的資料轉成了視圖所需要的格式。現在最後的步驟就是控制器要把自己作為FaceView的資料源。
現在回到FaceView中,有個小竅門:按下command+shift+O鍵然後鍵入關鍵字就能快速找到工程中的檔案,當你的工程逐漸複雜起來的時候這是個非常不錯的辦法。
注意現在我們的storyboard中顯示的是FaceView,記得之前我們設定的@IBdesignable吧?我們需要在控制器上拖拽一個faceview來顯示
然後給它增加一個屬性檢測器,這個檢測器會在IOS啟動這個應用并且加載這個storyboard的時候被調用,這是個很好的時機
另外一件要做的事情是每次我們的模型有變動時,我們要讓faceView重繪自身。我們需要補全updateUI這個方法:
這裡的意思就是調用我的drawRect函數,重繪我。如果不做這一步那麼修改happiness的值小人臉的表情不會有變化。我們把happiness的值修改為75,來運作一下看看效果:
一個很可愛的笑臉