天天看點

【我們都愛Paul Hegarty】斯坦福IOS8公開課個人筆記4 MVC enum Tuple Dictionary

 回顧一下我們上一話中的代碼:

上面這部分代碼跟控制器如何展示視圖沒有任何關系,隻涉及到資料的運算,viewcontroller中的其他代碼都是跟視圖有關,而這部分代碼會求出我們所需要的資料,是以應該分離出來作為MVC中的M。建立一個swift檔案,點選頂部工具欄中的file,選中newfile,選擇swift file。

【我們都愛Paul Hegarty】斯坦福IOS8公開課個人筆記4 MVC enum Tuple Dictionary

命名為CalculatorBrain,Swift中類的名字第一個字母大寫,如果名字由多個單詞組成,那麼每個單詞的首字母大寫,不需要用任何分隔符号隔開,這種命名方法成為駝峰命名法。

打開新檔案,裡面的代碼很簡單,除了頭注釋之外隻有一行:

Foundation是一個核心的服務層,它沒有關于UI的太多東西,這很好因為我們的Model應該是獨立于UI的,永遠都不要在模型中導入UIKit。我們在其中建構我們自己的類,不需要繼承其他類,這隻是個最基本的類:

這個類中的資料結構不同于之前在viewcontroller中的資料結構,除了儲存double類型的操作數,我們還需要儲存操作符,初始化這樣一個數組,用來模拟堆棧:

Op類型采用enum(枚舉)類型,枚舉的定義方法和類很相似,枚舉也可以有方法和屬性,但是它的屬性隻能是計算類屬性,并且枚舉是沒有繼承特性的。來說說什麼時候該用枚舉,當你的某樣東西在某種情況下是一個值,在另一情況下是另一個值,但是不可能同時擁有這兩個值的時候,使用枚舉是非常好的。

如果是在其他程式設計語言中,枚舉顯然已經沒法繼續定義了,但是Swift有個非常酷的特性,你可以将資料與枚舉中的case關聯起來,如下:

怎麼樣,這樣是不是一目了然,如果是操作數,那麼就是個Double的類型,如果是運算符,那麼就有一個String來描述它,還有一個函數來做運算,注意在Swift中函數也是一種資料類型。很多時候我們用一對花括号{}寫一個閉包來實作這個函數,或者在類中定義一個真正的函數。除了一進制運算,我們還需要一個二進制運算:

下面來簡單說一下API,api代表 application programing interface,API是你類中所有屬性和方法的描述。

上面的方法用來向棧中壓入一個枚舉類型,可以看到我們很容易就把double類型與枚舉關聯起來了。現在我們來建構操作符的方法:

這個方法的參數是加減乘除等運算符,那麼如何存儲這些運算符和相應的運算呢,為了程式的可擴充性,我們使用字典這個結構:

使用這兩種寫法都是可以的,當然第一種寫法比較簡單,也是推薦的寫法。下面介紹一點初始化的内容,上一話中也說過了,類初始化的時候必需保證類的所有屬性都被初始化,我們使用init(){}這個方法來初始化一個類,當你使用語句:

的時候,因為沒有參數,是以調用的就是CalculatorBrain中的init()這個方法。我們在init方法中初始化字典的值:

大家應該還記得閉包的用法吧。最後一個開方的運算我們知道Swift有類的自動識别功能,那麼這裡的運算是傳入一個Double的值然後傳回一個開放後的Double值,運用了一個函數sqrt,那麼我們可以這樣寫:

那麼問題來了,上面的加減乘除運算能不能寫成這樣的形式呢?答案是YES!因為在Swift中+、*這些運算符号本身就是方法,隻不過它們被設定成可以放到兩個運算數中間而不用通過()指派的寫法,進一步簡化後的初始化方法如下:

我們的加法和乘法滿足交換律,而因為減法和除法的運算數是反着的,是以不能簡寫。

每當CalculatorBrain被建立的時候,這些運算就會被建立。我們在方法中取到運算類型。

那麼option是什麼類型呢,你可能覺得它是個Op,但其實它的類型是Optional Op,因為你的傳入的symbol可能不存在在我們的knownOps定義中,這時候就會傳回nil,代表沒有找到。是以在字典中查找東西的時候總是會傳回可選型的值。

下面來說下Swift裡面的公有和私有,在類中如果你想要某個方法或者屬性是私有的那麼就在它的定義前面加上private,如果不加,那麼預設都是共有的。我們需要讓需要私有的東西私有化,以保證它不會被其他的程式改動。在這個程式中,壓棧和初始化這些操作應該是公有的,而Op應該是私有的,是以我們需要:

這個時候你會發現類中所有使用Op的方法或屬性都會提示錯誤,因為Op是私有的,是以這些操作也必須是私有的。現在我們需要一個方法來傳回OpStack中的運算結果。

這個運算的傳回值也是可選型,因為如果棧中的元素不能進行運算那麼結果會傳回一個nil。

這種運算是遞歸的,比如我們壓入4和5,然後壓入一個+,那麼運算的時候會先取出+,再擷取5和4作為加法的運算數,得到結果9,把9壓入棧中。如果有多個運算,比如棧中的值是:* 4 + 5 6.那麼首先擷取乘法運算,然後得到乘法的第一個運算數4,然後獲得加法運算,那麼會首先進行加法運算,把得到的結果11壓入棧中作為乘法運算的第二個運算數,最後得到結果44壓入棧中。

我們需要繼續定義一個同名的方法:

雖然同名,但是程式可以根據參數的不同而調用不同的方法,它的傳回值比較有意思,這是一個Tuple(元組)類型,逗号分隔開它的元素,你可以給每個元素命名來作為鍵值。那麼我們在方法中使用如下代碼:

上面的代碼标示如果操作棧不是空的話,我們希望可以在删除棧中最後一個元素的同時取到它的值,但是你會發現這句報錯了。為了解釋這個問題,首先介紹一些相關概念,Swift中類和結構體的用法非常類似,但是它們有兩個差别:1類可以繼承,但是結構體不能。2結構體傳遞的是值,而類傳遞的是引用。結構體通常僅用于基礎的資料類型,比如數組、字典這樣的元素,甚至Double和Int都是結構體,這很棒因為它們可以有自己的方法。是以在evaluate方法中無論我給ops數組,它都會被拷貝。其次在你引入參數的時候其實它的前面隐含了一個let:

也就是說它們是隻讀的,是以這個ops數組我們既不能給它添加元素,也不能删除它的元素。你可以把在引入參數時加一個var,這樣就不會報錯了,但是這是ops依舊是一個拷貝,我們不想這樣做,更好的做法是類類中初始化一個局部變量

你可能覺得這麼複制不是會拖慢程式的速度麼?但其實Swift不會真的複制它,直到你真正的改變它的時候,是以當傳入一個拷貝的時候,并不是真的複制了,而是傳入一種指針,一種它知道從哪來的指針,不是通過引用而是通過值。甚至這個數組有一萬個元素,它也不會做一萬次複制,隻有在我真正改變的地方,它就不得不進行複制了,并且這可能甚至不會做一個完整的複制,它可能隻去追蹤改變的地方,這真的很智能。現在我們用開關語句來判斷得到的棧頂Op

這裡我用了一個let來定義case中Operand這種情況中相關的Double,傳回的格式和我們方法定義的傳回類型是一緻的,下面完善這個Switch:

這是一個标準的遞歸,如果不了解的可以多看幾遍仔細想想,遞歸到操作數就傳回數值,如果遞歸到了操作符就繼續遞歸直到遞歸到最底層再逐層上彈,在遞歸過程中隻要發生無法計算的情況就會傳回nil。大家可能注意到switch中沒有寫default,這是因為枚舉類型Op隻有三種情況,我們在Switch中已經考慮了全部情況,是以不用寫Default方法。

現在讓我們來完善那個沒有參數的evaluate方法:

這次我們定義了一個局部的元組,evaluate傳入了整個opStack。

下一步把運算模型的代碼和控制器相關聯起來,有時候使用聯合視圖的時候左邊和右邊不能展示我們想要的内容,你會發現每次點選工程目錄中的檔案都出現在左邊,那麼如何出現在右邊呢,按住option鍵再次點選檔案,就會在右邊的螢幕上打開。現在我們在左邊展示CalculatorBrain代碼,右側展示ViewController的代碼:

【我們都愛Paul Hegarty】斯坦福IOS8公開課個人筆記4 MVC enum Tuple Dictionary

删掉viewController中以前寫的計算代碼,就是那些我們在本話最開始展示的代碼,現在viewController中的代碼簡潔多了,因為我們控制視圖有這些代碼就夠了。

甚至appendStack也不需要了,因為我們在CalcuatorBrain中有opStack來處理。現在在vc中增加初始化一個CalcuatorBrain:

我們需要修改CalcuatorBrain中的兩個方法,讓它們有傳回值。

也就是說我們每次點選鍵盤都會進行evaluate操作,這些操作是可選類型的,作為傳回值,這些傳回值将用來在vc中進行判斷,顯示在視圖中。

vc中的enter方法代碼修改如下:

vc中的operate方法修改如下:

這就是IOS開發的神奇之處,你隻用很少的代碼就可以控制流程。我們的電腦不僅功能完善,還具有很好的擴充性,你可以自己定制運算。

現在來運作下試試有沒有錯誤,可以看到界面沒有變化,我們并沒有修改控制器和模型的互動部分。

我們輸入13按一下enter鍵,可以看到label中顯示的是13.0,證明enter功能沒有問題,然後輸入 5和一個+,結果顯示如下:

【我們都愛Paul Hegarty】斯坦福IOS8公開課個人筆記4 MVC enum Tuple Dictionary

如果我們故意輸錯:

【我們都愛Paul Hegarty】斯坦福IOS8公開課個人筆記4 MVC enum Tuple Dictionary

結果正确,但是這并不是一個好的提示。

最後要做的是把整個計算流程可視化,我們要做的就是用println顯示棧中的内容:

這種寫法是不是超酷的?運作一下看看,輸入一個3:

【我們都愛Paul Hegarty】斯坦福IOS8公開課個人筆記4 MVC enum Tuple Dictionary

再輸入一個4:

【我們都愛Paul Hegarty】斯坦福IOS8公開課個人筆記4 MVC enum Tuple Dictionary

再點選一個+:

【我們都愛Paul Hegarty】斯坦福IOS8公開課個人筆記4 MVC enum Tuple Dictionary

數組中的Op再被轉化成String的時候,系統并不知道該如何轉化,是以把它顯示成了Enum Value。那麼該如何把它識别成一個String呢,做法是在Op的定義中添加計算屬性:

這裡的Printable并不是類,這也不是個繼承關系,它是個接口,現在我們運作一下看看:

【我們都愛Paul Hegarty】斯坦福IOS8公開課個人筆記4 MVC enum Tuple Dictionary

現在正常了,大家來試試吧。

繼續閱讀