close

繼上面一堂史丹佛 Swift 課程第一節,今天我們進入第二節的內容。

一樣先附上影片的 Youtube 的超連結如下。

這堂課一開始,講師就補充了一個似乎很重要的觀念,為什麼 “@IBOutlet weak var display: UILabel!” 中,display: UILabel! 不需要賦予一個值來初始化。

這是因為,事實上它是一個 "optional" 的型態並且已經在一開始就被賦予了初始值 "nil" 。

接下來又延伸出一個新問題,"UILabel!" 和 "UILabel?" 這兩者同樣都是 optional 的型態,那們他們有什麼不一樣?

型態上的確一樣,但使用的效果卻大大不同了。

若是使用問號 "?" 的話,那麼 display 這個變數是一個未被解包 (unwrap) 的變數,之後它在與其他變數做動作時就會因為型態上的不同而導致錯誤。

所以每當要進行這類動作時,我們都必須在 display 的後面加上一個驚嘆號 "!" 來解包,非常麻煩。

而使用感嘆號 "!" ,則可以將 display 這個變量在一開始就解包,這樣後面就省下另外解包的麻煩了。

"UILabel" 的文檔內容如下:

Declaration :

class UILabel : UIView, NSCoding

Description :

The UILabel class implements a read-only text view. You can use this class to draw one or multiple lines of static text, such as those you might use to identify other parts of your user interface. The base UILabel class provides support for both simple and complex styling of the label text. You can also control over aspects of appearance, such as whether the label uses a shadow or draws with a highlight. If needed, you can customize the appearance of your text further by subclassing.

Overview

The default content mode of the UILabel class is UIViewContentModeRedraw. This mode causes the view to redraw its contents every time its bounding rectangle changes. You can change this mode by modifying the inherited contentMode property of the class.

New label objects are configured to disregard user events and clip subviews by default. If you want to handle events in a custom subclass of UILabel, you must explicitly change the value of the userInteractionEnabled property to true after initializing the object. If you want to allow subviews to extend beyond the bounds of a label, you must explicitly change the value of the label’s clipsToBounds property to false.

For information about basic view behaviors, see View Programming Guide for iOS.

For more information about appearance and behavior configuration, see Labels in UIKit User Interface Catalog.


接下來進入今天的計算機新內容。

首先,大家都知道計算機上面有個 "Enter" 按鍵,那我們當然也必須將它設計出來,以下就是這個按鍵所對應的編碼。

這裡出現了很多新的內容,讓我們一個一個來做解釋。


與前面的數字按鍵相同,輸入鍵 "Enter" 也同樣必須將它放入編碼之內, "IBAction func enter() { }"。

但不同於前面數字按鍵的 "IBAction func numberkey(sender: UIButton) { }" ,Enter 不需要回傳資料,所以內部不需要有 "sender"。

當輸入鍵 Enter 被點擊時,我們希望進行幾個動作。

1. 將 displayvalue 回傳為 false ,這是為了使 Enter 不會在點擊之後被疊加顯示在螢幕上。

2. 新增一個變量 "enteredvalue" ,型態為陣列 "Array",並定義內部的型態為 "Double",Double 代表的是帶有小數點的數值。

var enteredvalue: Array<Double> = Array<Double>()

因為 Swift 是強類型語言,它能夠自動做類型推斷 (Type Inference),所以當我們有賦予一個初始值後,我們就不需要將型態特別寫進去,所之後可以把這段編碼簡化為下面的結果。

var enteredvalue = Array<Double>()

接著定義它的動作為將 "displayfinalvalue" 的結果照順序寫入陣列的內容中。

enteredvalue.append(displayfinalvalue)

例如當我輸入 "1" 並按下 Enter ,enteredvalue 就會變為 [1.0] 。

接著我們再輸入 "2" 並按下 Enter ,enteredvalue 就會變為 [1.0, 2.0] ,以此類推。

".append" 的文檔內容如下 :

Declaration :

mutating func append(newElement: Element)

Description :

Append newElement to the Array.

Complexity: Amortized O(1) unless self's storage is shared with another live array; O(count) if self does not wrap a bridged NSArray; otherwise the efficiency is unspecified..

上面的 append(newElement: Element) 意思是所新增的內容之型態必須與括弧內的參數之形態相同。


前面我們既然使用了一個新的變數 "displayfinalvalue" ,那麼當然要對它做定義。

var displayfinalvalue: Double { }

在這個宣告之下,大家可以看到 "get{ }" 與 "set{ }" 兩個屬性,他們被稱為 “計算屬性 (Computed Properties)”。

除了存儲屬性 (let, var) 外,類 (Class)、結構體 (Struct) 與枚舉 (Enumeration) 都可以定義計算屬性,它不直接存儲值,而是提供一個 getter 來獲取值,一個可選的 setter 來間接設置其他屬性或是變量的值。

於是我們先用 setter 來做設置。

先將螢幕顯示的結果 "display.text" 寫為 "newValue",記得 "V" 要大寫。"newValue" 則是 setter 中的默認初始值名稱,不喜歡的話可以另外定義其他名稱。

接著將 displayvalue 改為 false ,然後將這兩個條件回傳到 getter 做下一步動作。

getter 中寫入了下面的編碼。

return NSNumberFormatter().numberFromString(display.text!)!.doubleValue

這段編碼講師並沒有做說明,而是作為加分的學生作業,必須自行查文檔才能理解,下面我幫各位做個解釋。

根據文檔說明,這段編碼的意思是,由 "NSNumberFormatter" 這個類 (Class) 的型態轉換功能中調用 "numberFromString" 這個函數將 "display.text!" 由 String 轉換為 數字型態 NSNumber,接著調用其中的 "doubleValue" 屬性將型態限定為 "Double" ,最後回傳給 displayfinalvalue 。

"displayfinalvalue" 這個屬性只有在點擊輸入鍵 (Enter) 時才會被調用,所以以上的計算屬性也只有在那個時候才會動作。


"NSNumberFormatter" 的文檔內容如下:

Declaration :

class NSNumberFormatter : NSFormatter

Description :

Instances of NSNumberFormatter format the textual representation of cells that contain NSNumber objects and convert textual representations of numeric values into NSNumber objects. The representation encompasses integers, floats, and doubles; floats and doubles can be formatted to a specified decimal position. NSNumberFormatter objects can also impose ranges on the numeric values cells can accept.

Ps : what's NSNumber ?

NSNumber is a subclass of NSValue that offers a value as any C scalar (numeric) type. It defines a set of methods specifically for setting and accessing the value as a signed or unsigned char, short int, int, long int, long long int, float, or double or as a BOOL. (Note that number objects do not necessarily preserve the type they are created with.) It also defines a compare: method to determine the ordering of two NSNumber objects.

Ps : what's NSValue ?

An NSValue object is a simple container for a single C or Objective-C data item. It can hold any of the scalar types such as int, float, and char, as well as pointers, structures, and object id references. Use this class to work with such data types in collections (such as NSArray and NSSet), key-value coding, and other APIs that require Objective-C objects. NSValue objects are always immutable.


".numberFromString" 的文檔內容如下:

Declaration :

func numberFromString(string: String) -> NSNumber?

Description :

Returns an NSNumber object created by parsing a given string.

An NSString object that is parsed to generate the returned number object.


".doubleValue" 的文檔內容如下:

Declaration :

var doubleValue: Double { get }

Description :

The number object's value expressed as a double, converted as necessary. (read-only)


接下來我們將運算符號加進我們的計算機功能裡面。

將運算符號拉進 ControlView 裡面並賦予函數屬性 "operate()" 。

由於我們有四個運算符號要設置,所以皆將其指向 operate 屬性並使用 switch 語句設定不同的模式來針對運算符號的不同做不同的動作。

switch 的使用方式是將某個值帶入不同模式,所以我們必須先定義一個屬性,這裡設置 operation 來表示 運算符號之按鍵的當下內容。

switch 的使用格式如同下面敘述:

switch some value to consider { case value 1: respond to value 1 case value 2, value 3: respond to value 2 or 3 default: otherwise, do something else }

而在我們的編碼中,設置 "+"、"-"、“x” 與 "÷" 四個 case 去做條件設置。

以加法器為例做說明。

我們設定當 "enteredvalue.count >= 2" 時執行 "displayfinalvalue = enteredvalue.removelast() + enteredvalue.removelast()" 這個動作,並且調用 Enter() 來將結果寫入 enteredvalue 的陣列中並顯示於我們的螢幕上。

".count" 這個屬性可以幫助我們導出陣列內的資料之數目,於是我們可以用 "enteredvalue.count" 來得到 enteredvalue 的棧內資料數目,並設定當其多於兩個以上時在進行動作。

這個條件相當合理,畢竟如果只有一個資料時無法做相加的動作。

".count" 的文檔內容如下:

Declaration :

var count: Int { get }

Description :

The number of elements the Array stores.


至於用什麼東西來做相加呢?

前面我們設置了一個 enteredvalue 屬性來將輸入過的數值存儲在棧 (stack) 裡面,所以我們可以藉由 enter() 將想要做運算的數值存儲,接著利用 "enteredvalue.removelast()" 將棧內最後一個資料調用並且從棧內刪除,然後進行運算。

於是 "displayfinalvalue = enteredvalue.removelast() + enteredvalue.removelast()" 就會將棧內的最後兩個資料取出並且刪除,然後做相加得到一個新的資料 "displayfinalvalue" 。

".removelast" 的文檔內容如下:

Declaration :

mutating func removeLast() -> Element

Description :

Remove an element from the end of the Array in O(1).

Requires: count > 0.


條件內的最後有一個調用 enter() 的指令,這是為了將剛剛得出的結果再重新寫入棧內,並且將其顯示於螢幕上。

這裡可能有人會問,哪一段編碼可以將運算出來的結果顯示於螢幕上?

這個部分講師並沒有明確說明,但我個人判斷應該是藉由前面的存儲屬性 "var displayfinalvalue: Double { }" 將其寫入。

set {display.text = "\(newValue)"

            displayvalue = false }

setter 內有一行是將螢幕的顯示結果 "display.text" 設置為 newValue 的值,當我們完成加法計算而導致 displayfinalvalue 得到新的結果時,可能就使屬性設置內的 setter 動作,而讓 newValue 得到新的運算結果並導入 display.text 。

不過原本我認為 setter 只是一個暫時的用來間接設置其他屬性的屬性,並無法對括弧外的屬性生效,但看來並不是這樣。

這部分宅宅我還必須再好好研究一番呀...


其他的運算條件就參照加法的處理就可以完成了。

不過這樣子寫完會發現整個程式碼過於冗長,接下來試試看能否將它簡化吧。

上面的程式碼試著用函數設置的方式將編碼簡化,作法就是將原本 case 內的敘述拉出來獨立作為一個函數 performoperation(operatoin: (Double, Double) -> Double) { } 。

並將原本敘述中 "displayfinalvalue = enteredvalue.removelast() + enteredvalue.removelast()" 這個運算式改為用另一個函數調用的方式來得到。

displayfinalvalue = operation(enteredvalue.removelast(), enteredvalue.removelast())

"operation()" 是用來代指四個運算動作的函數,所以我們必須另外在設置四個函數來讓 performoperation() 調用。

以加法為例,新增一個函數 plus(op1: Double, op2: Double) -> Double { } ,從外部引入兩個數值分別取代 op1 與 op2 ,然後回傳 op1 + op2 。

於是當我們將 case "+" 執行 performoperation(plus) 時,它會將 performoperation() 函數中的 enteredvalue.removelast() 導入 plus() 取代 op1 與 op2 做運算,並將結果回傳給 displayfinalvalue。

同理,其餘三個運算模式也可以這樣做修改,不過似乎還是有點繁瑣。


Swift 有一個語法叫做"閉包 (Closure)" ,他是種自包含的函數代碼塊,可以在代碼中被傳遞或使用。

閉包可以捕獲和存儲其所在的上下文中任意常量和變量的引用,Swift 會為我們管理在捕獲過程中涉及到的所有內存操作。

這種語法相當被鼓勵用來在常見的場景中用來做語法優化,它的優化內容如下敘述:

1. 利用上下文推斷參數和返回值類型。

2. 隱式返回單表達式閉包,即單表達式閉包可以省略 return 關鍵字。

3. 參數名稱縮寫。

4. 尾隨 (Trailing) 閉包語法

下面是閉包表達式的一般語法:

{ (parameters) -> returnType in

    statements}

接下來我們用閉包來進一步簡化我們的程式碼。

同樣以加法為例,將 performoperation(plus) 中的 "plus" 函數直接用它的敘述來取代。

func plus(op1: Double, op2: Double) -> Double {

            return op1 * op2 }

parameter : op1: Double, op2: Double

returnType : Double

statements : return op1 +op2

故根據閉包的語法,將 performoperation() 改為下列形式:

performoperation({(op1: Double, op2: Double) -> Double in

            return op1 + op2 })

其他運算模式同樣這樣簡化,而原本的函數敘述就可以刪除了。


事實上,這段編碼還可以再更加簡化一些,方法就是利用 Swift 強大的類型推斷功能。

原本宣告 performoperation() 函數時就已經定義了型態等資訊,所以當在 switch 裡面調用函數時就不需要再另外敘述型態等資訊了。

於是程式碼可再簡化如下。


最後還有兩個特性可以將這段編碼簡化到極致。

1. 函數內的資料如果我們不幫他命名的話,它會按照順序從頭開始分別預設為 $0, $1, $2, $3, ...... 。

所以若是我們不特別設置 op1 與 op2 為名稱的話,可以直接用 $0 與 $1 來表示,這樣就連 (op1, op2) 這個名稱宣告都可以省略了。

2. 當我們有一個函數 A() ,而裡面有個函數 B() 作為參數時,且 B() 為其最後一個參數,那麼我們可以把 B() 的敘述拉到 A() 之外。

因此,讓我將上面的編碼做最後的簡化如下。

在最後講師留了幾個按鍵為空白,是作為課堂學生的作業,我個人加入了 "Expential" 、 "Log10" 與 "Clear" 三個按鍵,大家可以參考我的編碼如下圖。

至此,這個簡單的計算機就完成了。


這堂課接下來的內容是 MVC 的介紹。

MCV 是一個基本機制,用於分類程序中的所有對象到三個 Camp, Model, View 和 Controller。

模型 (Model) 是那些對象的集合,所謂的那些對象是指程式的行為 (Application does)。

Model = What your application is (Not how it is displayed)

例如在前面我們所做的計算機中,計算就是模型。

控制器 (Controller) 控制模型如何顯示在螢幕上。

Controller = How your controller is presented to your user (UI Logic)

視圖 (View) 是控制器會用到的一種附屬類。

View = Your controller's minions

正確的 MVC 不僅僅知道內容的存放位置,同時知道三層 (Camps) 是如何通訊的。

簡單總結這三層的通訊特性與規則:

1. Controller can always directly talk to their Model.

2. Controller can always directly talk to their View. (Outlet 屬性)

3. Model and View should never speak to each other.

4. Can View speak to its Controller ? 

a. Sort of communication is "blind" and structured. The Controller can drop a Target on itself and hand out an action to the View. (Button 屬性)

b. Sometimes the View needs to synchronize with the Controller. The Controller sets itself as View's delegate. (will, did and should 屬性)

c. The delegate is set via a protocol. (It's blind to class)

5. Views do not own the data they display. So if needed, they have a protocol to acquire it. Controller almost always that data source. (Not Model)

所以控制器 (Controller) 的工作是給視圖 (View) 解釋並格式化模型 (Model) 提供的數據。

6. Can Model talk directly to the Controller ? No, the Model is (should be) UI independent.

a. So what if the Model has information to update or something? It use a "Radio-Station" like broadcast mechanism. (Notification & KVO) 

b. Controller (or other Model) tune in to interesting stuff. A view might "tune in", but probably not to a "Model's" station.


當我們想要製作更加複雜的應用時,可以將多個 MVC 做結合?

一個完整的 MVC 可以作為另一個 MVC 的視圖 (View) 的一部份。


到此,這堂課差不多就告一段落了。

我們下一節課再見吧~


By 宅宅阿軒

arrow
arrow
    文章標籤
    iOS8 Swift
    全站熱搜

    Cloud 發表在 痞客邦 留言(0) 人氣()