2014年12月7日 星期日

Swift - Struct 與 Class 的差異性

Hi, There。今天這個篇文章要來介紹 Swift 中 struct 和 class 有什麼不一樣的地方?首先要先和大家提到一個觀念,Value Type 和 Reference Type 其中 struct 是 Value Type 而 class 是 Reference Type 所以這篇文章呈現的 struct 的行為也可以套用到所有的 value type 物件,相同地 class 的行為也可以套用到 reference type 的物件上。

我們來建立一個自已的 struct 名稱為 SRectangle 。程式碼如下呈現。

struct SRectangle {
 var width = 200
}
這個 struct 有一個 property 名為 width 。
再來建立一個 class 名為 CRectangle 也有一個叫做 width 的 property 程式碼如下

class CRectangle {
 var width = 200
}
準備好了,我們先來看第一個差異點

1. 建構物件時

struct


var sRect = SRectangle()
// 或者 

sRect = SRectangle(width:300)

sRect.width // 結果就是 300

class


var cRet = CRectangle()
// class 不能直接用 CRectangle(width:300) 必需要定義一個 constructor
cRect.width // 為 200

主要的差別就是 class 在產生物件時不能很自然把 property 放在 constructor 的參數裡

2. 指定給另一個變數的行為不同

struct


var sRect = SRectangle()
// 或者 

var sRect2 = sRect

sRect2.width // 目前值是 200,因為 sRect 直接 copy 一份完整記憶體給 sRect2
sRect2.width = 500
sRect.width // sRect.width 值不受 sRect2 影響還是 200

class


var cRect = CRectangle()
// 或者 

var cRect2 = cRect

cRect2.width // 目前值是 200,因為 sRect 直接 copy 一份完整記憶體給 sRect2
cRect2.width = 500
cRect.width // cRect.width 也改變成了 500

以上是 value type 和 reference type 最大的不同,在每次做 assignment 的時候,value type 都會複製一份完整相同的內容給另一個變數,而 class 則是把記憶體的位置給變數。所以 reference type 大多會保有一份記憶體而重覆利用。

3. 關於 immutable 變數

在 Swift 這個語言的特色之一就是可變動內容的變數和不可變動內容的變數用 var 和 let 來區別。如果一開始用 let 來修飾變數就會造成 compiler 的錯誤。如下

var name = "Michael"
name.write(" Pan") // 這樣是正常

let name = "Michael"
name.write(" Pan") // 會造成 compile 錯誤

struct

大至也遵循這個規則如下

let sRect = SRectangle()

sRect.width = 500 // 會造成錯誤

class


let cRect = CRectangle()

cRect.width = 500 // 預設可以直接更改雖然是 let 修飾的 cRect

let 這個效果無法延申至 class 。不過要提醒大家在 Swift 常用的 String, Array, Dictionary 都是 struct 所以 let 是會有效果的

4. mutating function

我們先要為 struct 和 class 新增一個 method 名為 changeWidth() 而且我們在不改變原始的程式碼為前提下,新增一個 method。如下

struct


struct SRectangle {
    var width = 200
    
}

extension SRectangle {
    mutating func  changeWidth(width:Int){
        self.width = width
    }
}

class


class CRectangle {
    var width = 0
}

extension CRectangle {
    func changeWidth(width:Int){
        self.width = width
    }
}

這裡用了 extension 新增 class 裡的 method 而不需要改到原本的程式碼。 以。struct 和 class 的差別是 struct 的 function 要去改變 property 的值的時候需要加上 mutating 字樣,而 class 不用這樣做

5. Struct 和 Class 裡的 function 共同特色 - Implicit External Parameter Name

function 在定義時,有一種特別的寫法叫做 external parameter name 舉例來說 一般寫法

func setSize( width:Int, height:Int) {
    println("width \(width), height \(height)")
}

setSize(50, 100) // 直接丟入參數
External Parameter Name

func setSize(width width:Int, height height:Int) {
    println("width \(width), height \(height)")
}

setSize(width:50, height:100) // 必需在參數值前加上 external parameter name

另一個簡寫

func setSize(#width:Int, #height:Int) {
    println("width \(width), height \(height)")
}

setSize(width:50, height:100) // 當 External name 和 internal name 一樣的時候可以在 internal name 前面加上 # 就可以少寫 external name

這樣的行為對於一般 function 並沒有強制一定要寫 external name 不過在 struct 和 class 裡是強制使用的。如下

struct


struct SRectangle {
    var width = 200
    
}

extension SRectangle {
    mutating func  changeWidth(width:Int){
        self.width = width
    }
    func setName(name:String, width:Int) {
        println("name \(name), width is \(width)")
    }
}
var sRect = SRectangle()
sRect.setName("Michael", width: 500)


class


class CRectangle {
    var width = 0
}

extension CRectangle {
    func changeWidth(width:Int){
        self.width = width
    }
    func setName(name:String, width:Int) {
        println("name \(name), width is \(width)")
    }
}

var cRect = CRectangle()
cRect.setName("Michael", width: 500)

無論 struct 或是 class 裡的 function 第二個參數開始都強制加入 external parameter name。在定義的時候不用特別寫,就會有了。

6. 繼承

物件導向語言,令人覺得強大的能力莫過於繼承,而 struct 沒有 繼承的功能,只有 class 有。如下

class


class CRectangle {
    var width = 0
}

extension CRectangle {
    func changeWidth(width:Int){
        self.width = width
    }
}
class CSquare : CRectangle {  // :CRectangle 代表繼承的語法
    
    // CSquare 這個 class 看起來什麼內容都沒有
}

var cSquare = CSquare()
cSquare.changeWidth(300)  // 可以直接使用父類別的 function
cSquare.width  // 也可以直接使用從父類別繼承下來的 property


以上是簡單常用到的行為裡 struct 和 class 常令使用者搞混的情況,希望對大家有幫助。 歡迎提供更多建議。

2014年10月27日 星期一

關於 Swift 繼承的兩三事

談到繼承就要用先寫一下 Swift 這個語言的 class 的寫法:

class Rectangle {  // 非正確寫法
  var width:Double
  var height:Double
}

這個就是 swift class 的定義方式,名稱為 Rectangle。有兩個 property 各是 width 和 height。 但是這樣寫並不算完成,因為 Swift 要求在產生這個 class 的實體(instance) 的時候所有的 property 都要有明確的值。所以我們來加上初始值

class Rectangle {
  var width:Double = 200.0 
  var height:Double = 300.0
}

這樣簡單的 class 就準備完成,然後我們需要來產生實體如下

var rect = Rectangle()
這樣就可以用 rect 來使用實體,然後改變 property 的內容如下

var rect = Rectangle()
rect.width = 50.0
Rectangle() 做了什麼事?讀者一定很好奇。其實對熟悉 Objective-C 的開發者來說,他就是 alloc - init 這兩個動作的簡化。 在 Swift 裡 Rectangle() 就會對應到 class 裡面的 init()。比如我們改寫一下 Rectangle 成

class Rectangle {
  var width:Double
  var height:Double
  init(){
    width = 200.0
    height = 300.0
  }
}

多了一個 init() 而且 var width 和 var height 之後並沒有給值,這樣可以嗎? 這樣是可行的,因為在真正使用實體前,init() 會最先被呼叫,所以在 init() 裡面把 propety 值都設定好,就可以保證之後存取 property 都不怕沒有值。 讀者可以直接在 playground 上面試試,init() 這個第一個被執行的 method 也有一個別名叫做建構子 - constructor。 然後我們來新增一個 method 名為 area 如下

class Rectangle {
    var width:Double
    var height:Double
    init(){
        width  = 200.0
        height = 300.0
    }
    
    func area() -> Double {
        return width * height
    }
}
var rect = Rectangle()
rect.area() // 會得到 60000 
以上,就完成一個很基本的 Class 的定義。接下來才是繼承的問題 XD 所以我們來產生另一個 class 名為 Square,繼承 Rectangle 如下

class Square : Rectangle {
    
}
var s = Square()
s.height = 20.0
s.width = 20.0
s.area()
Square 裡面空空如也,只有一個 : Rectangle 的宣告,這樣一來 Square 就擁有 Rectangle 所有內容,包含 width 和 height 的 property 還有 area() 這個 method 自然也可以直接使用 這個就是繼承的好處 接下來我們來把 Square 做一些修改,比較符合我們的期待。

class Square : Rectangle { // 非正確寫法
    var side:Double
}

新增一個 side 的 variable 這樣一來要記得在 init() 指定給 side 值。 但是因為 Square 繼承 Rectangle ,已經有一個 init() 所以在 Square 的 init() 前面要加上 override (覆寫) 字樣。如下

class Square : Rectangle {
    var side:Double
    override init(){
        side = 50
    }
}
當子類別(指 Square) 要重寫一個和父類別(指 Rectangle)一樣的 method 的時候就要加上 override 的字樣,這樣執行的時候就是採用子類別的 method 程式碼而不是父類別的程式碼。 這樣一來就可以執行如下程式碼

class Square : Rectangle {
    var side:Double
    override init(){
        side = 50
        // 最後會執行 super.init()
    }
}
var s = Square()
s.area() // 得到 60000
有沒有發現一個奇怪的地方?按照推理 Square() 會去執行 Square 的 init(),裡面只有 side =50 ,s.area() 是去執行 Rectangle 裡面的 area() 程式碼,因為在 Square 並沒有 override area()。但 init() 裡面沒有對於 width 和 height 有給定值,s.area() 竟然會有值出現。由此我們可以推論 override init() 最後會去執行父類別的 init(),也就把 Square 從 Rectangle 繼承來的 width 和 height 都給了值 但是還是怪怪的,Square 的 area() 竟然不是和 side 有關而是 width 和 height ,所以我們更新 Square 如下

class Square : Rectangle {
    var side:Double
    override init(){
        side = 50
    }
    override  func area() -> Double {
        return side * side
    }
}
所以我們覆寫了 area(),讓它和 side 有關。這樣如下的程式碼在 Square 的用法上就更合理

class Square : Rectangle {
    var side:Double
    override init(){
        side = 50
    }
    
    override  func area() -> Double {
        return side * side
    }
}
var s = Square()
s.side = 30.0
s.area() // 會得到 900.0
So far, So good 看起來很不錯,但還是有一個小小的缺點。也就是當我們用 Square 的 side 的時候,也可以用 width 和 height,這兩個從 Rectangle 得到的變數,但這三個就是不同的變數,這樣一來觀念上很奇怪。比較好的方式是希望使用 Square 的 width 的時候也就是和 side 有連帶關係。拿 width 來說,如果設定 width 的值同時就是設定 side 的值,取得 width 的值也就是取得 side 的值這樣會比較合理。於是我們要來用到 Swift 語言裡很特別的功能,就是 override property。如下

class Square : Rectangle {
    var side:Double
    override init(){
        side = 50
    }
    
    override  func area() -> Double {
        return side * side
    }
    
    override var width:Double{
        get{
            return side
        }
        set{
            side = newValue
        }
    }
}
覆寫 property var width:Double 的方法如下
  
    override var width:Double{
        get{
            return side
        }
        set{
            side = newValue
        }
    }
前面也要加上一個 override 然後要用 {} 包起來,裡面有 get 和 set 關鍵字。 get 很好理解就回傳 side 的值,set 的區塊裡面要用 newValue 這個保留字代表要設定給 width 的新值,然後指定給 side 相同地 height 也照辦如下
  
override var height:Double{
        get{
            return side
        }
        set{
            side = newValue
        }
}
這樣一來不論是存取 width 或是 height 都是存取 side。 很理想,來測試一下
  
var s = Square()
s.width = 50.0
s.area() // 得到 2500.0
做個這篇的小結

  1. 簡單 class 定義方式, 建構子 init() 使用時機 
  2. 繼承的寫法,init() 在子類別和父類別的使用時機
  3. 覆寫 method 和 property 的方法 
謝謝各位,大家下次見

2014年10月7日 星期二

如何讓 Swift 與 Objective-C 共生 - 無痛轉生

Hi There,  Swift 語言推出以來締造了許多的記錄,對於 iOS Developer 來說 Objective-C 是再熟悉不過了,現在有個新的小朋友 Swift 的出現,不知道需要需要再花時間來學習,這篇文章不是來教導怎麼學習 Swift 來開發 iOS 程式,而且先從 Swift 怎麼利用已定義好的 Objective-C 的 Class 來給想學或是正在學的 Developers 一個參考。
首先要介紹的是 Bridging Header 這個檔案。
首先 產生一個 Single View Application 的專案。如下圖
這個就是新的 Xcode 6 產生專案的畫面,比之前 Xcode 5 少了幾個 (少了幾個呢?留給讀者去比較了)。
接著我們要選擇 Swift 這個語言。如下
 命名為 SwiftObjc
然後我們要在專案的 Navigator 中新增一個 Objective-C 的 Class。如下。
記得要選的是 Cocoa Touch Class 而不是 Objective-C File 哦 。
命名為 Car。
記得 Language 是 Objective-C 哦。
接著不一樣的事情發生了。Xcode 會自動跳出一個提醒視窗。如下。

這個視窗的意思是說,Xcode 想幫開發者自動加入 bridging header 的檔案。
現在就先選擇 Yes 。來看看專案有什麼變化?如下
多了三個檔案哦。我們想要的 Car 這個 Class 的 .h, .m ,還有一個所謂的 Bridging Header 。在這的名稱為 SwiftObjC-Bridging-Header.h 。所以跳出視窗問的就是這個 Header,裡面長什麼樣子呢?基本上是空白。
因為我們要在 Swift Class 裡面用 Objective-C 的 Car class 。所以必需要加上如下的。程式碼。
就是 #import "Car.h" 。
也可以自已手動加,但需要改變專案的設定,如下圖
有一個 Objective-C Bridging Header 的設定要寫,記得除了寫 .h 檔名之外,還要加上 Product 名稱哦,通常就是 Project 名稱。
接下來我們就在 ViewController.swift 裡面使用 Car 這個 Class
如下
就可以直接在 viewDidLoad 裡面用 Car 這個 Class。而且用 Swift 語法
Car()
在 Swift 中 Car() 就是和 Objective-C 的
[[Car alloc] init ]
 而且聰明的讀者有沒有發現到,在 Swift 裡面每個 row 的程式碼最後都不需要分號 ; 結尾
接著我們來加一下 method 在 Car 裡面。如下新增

Method 的名稱叫 setPrice:andYear: 這樣的 selector 要怎麼在 Swift 呼叫呢?Swift 也是支援  selector 的觀念,如下使用。
setPrice:andYear: 在 Swift 的語法就是
func setPrice( a:Int, andYear: b:Int)
 這也是 Swift 跳脫了傳統的 C function 的束縛加了一個新的語法叫 external parameter name 。也就是 andYear: ,在 Swift 的 function 裡用第二個參數的 external parameter name 來表示。
接著執行是會 Crash。為什麼?因為 Car.m 並沒有實作 -setPrice:andYear:。所以記得要實作。如下。
順便加上兩個  ivar,各是 price 和 year
這樣一來在 ViewController.swift 就可以執行成功。
對於 Selector 的觀念,我們再來做一個小小的實驗。
先在 ViewController.swift 再加上一些程式碼

我們要利用 NSTimer 來呼叫兩個  function 。分別是
func action(a:NSTimer, second b:Int ) {
        println("action1")}
這個的 selector 寫成 Selector("action:second:")

以及
func action(action a:NSTimer, second b:NSTimer){
        println("action2") }
而這樣的寫法,無法對應到 Objective-C 觀念的 Selector
執行後會發現一直出現 action1 的 log是正常的。

接著我們來看一下存取 ivar。在這個時候如果直接從 Swift 存取 Car 的 ivar 會發生如下錯誤。


放在  .m 的 ivar 應該是無法被看到,如果試著把 ivar 拿到 .h 來如下

結果也是一樣。在 Swift 要正常存取 Objective-C 的 ivar 。最好的方式就是利用 property,將 Car.h 改寫如下


再利用 Swift 來執行如下
就可以成功執行。

最後要來解釋一下 Objective-C 的 class method 要怎麼在 Swift 被使用。
我們要在 Car.h 加上 class method 如下
當然 .m 要對應好

怎麼在 Swift 中使用 +sharedInstance 呢?如下

var myCar = Car.sharedInstance()

這樣就可以了。看起來不難,不過提醒讀者名稱這事對於 Objective-C 和 Swift 都很重要,如果有一個 class method 命名成如下
+(instancetype) car; 

在使用

var myCar = Car.car()
就會造成 compiler 的錯誤,目前可以的話就盡量避免,直接用 Class 名稱第一個字小寫的 class method。
談了這麼多,到底應用在真實的例子是如何呢?
我們就拿 AFNetworking 的 framework 來測試。

這是一個非常有名也很多人用在網路的資料的傳輸上的 open source 的 framework。
在這就不簡介。先把 AFNetworking 的檔案都拉到專案
然後在 Bridging Header File 也要加上 AFNetworking.h 如下
最後補上一段,下載 Image 的程式碼就大功告成了啦
最後回答大家常問麥老師的問題,Swift 是不是非學不可?Swift 是不是會取代 Objective-C ?
Swift 是不是非學不可?
在這一二年內答案不是,但是因為 Swift 的好學好寫,我想再過不久 Swift 的 framework 不會比 Objective-C 少,到時,如果用到只有 Swift 版的 framework 就是非學不可了。
Swift 是不是會取代 Objective-C ?
我個人是覺得再 5 年可能有機會,因為一個語言從出生到非常成熟可能需要 10 的年磨練,依照熱門程度不同,時間會有所調整,Swift 這麼火紅的情況下,可能會使它比較快穩定。我們可以看一個參考,如果 Apple 某個常用應用程式是用 Swift 重新改寫,完全取代 Objective-C 的日子就很近了。

以上一些,很久沒有寫文章的碎碎唸。希望大家有所收獲。請不吝指教。Michael 筆。

2014年3月9日 星期日

使用者導向設計 - User-Centered Design

最近在書架上看到一本很有趣的書
使用者導向設計
乍看之下是給設計師看的書,翻了幾頁之後才了解是對開發者的案件的討論比較多。主要在建議開發者多和使用者互動和討論,多了解使用者心意更能設計出好的產品。
傳統來說,討論電子產品和使用者的關係可以從人機互動介面 Human Computer Interface (HCI) 開始說起,一直延伸到近來非常熱門的 User Experience (UX) 的討論,這幾些和技術不太相關的名詞一直以來不被開發者重視,開發者多半會想把時間花在更好的技術討論上,殊不知如果多了解使用者的行為和心態,更可以創造出好的產品,也許不需要用到非常高深的技術。UCD 的討論就是從這樣的角度出發,試圖說服開發者,從另一個身份和角度來思考產品的設計。
UCD 主要重視 User-Centered Design 和 User Experience 這兩個討論主題,也就是從產生程式前的兩個要研究主題。
讓我們來介紹一下其中一個主題

與使用者合作

這是對於開發者而言,最難克服的一關卡。開發者在開會的時候,一聽到需求往往會先入為主的先思考每個需求會用到的技術是什麼?在平常有多時間開發者也會選擇去研究新的技術,而不會花時間去了解使用者,知道使用者的需求。這也是很正常的事,喜歡寫程式的人往往不喜歡和人打交道,同樣是要花時間為什麼不把時間多花在鑽研新的技術上面來的可能比較有更多的成就感。
UCD 要強調的是產品是給人用的,開發者如果可以多花一些時間在使用性價值或是了解使用者的心理上的話,對於開發一定是正面的幫助。愈了解使用者,開發出來的產品就會愈接近使用者的需求,開發者不妨試試先放下鍵盤,從另一個角度去思考,多和使用者聊天,也許就有意想不到的收獲。
一般我們可以把使用者分成幾個種類
  • 資訊提供狂
    • 這類的使用者提供過多的資訊,又不好意思請他們停止,最好是可以把資訊分類,還有設定優先順序,說真的,過份的時候可以暫時不理他們,不過,有提供想法總比什麼都沒有還好。
  • 控制狂
    • 這樣的使用者想要領導整個專案進行,他們也只是想要得到認同和尊重,往好處想,這類的使用者會主動找出好或不好的地方,有可能成為戰友。
  • 自以為代言者
    • 代言者的特色是提供負面的主意比正面的多,如果影響到團隊的進行,可以試圖請代言者以科學的方式和證據來佐證比主觀和個人的想法來的可以說服大家。
以上簡單介紹 UCD 重要的因素。有機會我們再來和大家討論更多的方法用來設計更符合人性的產品。最後還是提醒開發者,接近使用者,會有不一樣的靈感出現。