前言

敝社有個很棒的福利,就是會購買外部線上學習網站的課程。只要你還在公司服務,就可以上這些網站,點選你想要的課程來看,這大概是屬於工程師的終身學習概念 XD

最近因為想私下接觸一下 iOS App 的開發,於是這一個月就開始在看 lynda.com 上的 Swift Essential Training 課程,我自己是覺得課程內容很不錯,雖然比起線上文件來說可能淺了點,但看影片學習的明顯優點,就是記憶點會很明確,畢竟有人在前面邊講邊操作一次給你看,比起光讀文件來說,學習效果會更加的好。

我自己本來就有用 Evernote 記錄筆記的習慣,在上這門課的過程中,也一樣將筆記記錄在 Evernote 下︰Evernote 筆記版,本文算是 Evernote 筆記的 blog 版,但大體內容是差不多,有興趣的朋友可以挑一個順眼的版本來看就好。

筆記中的程式碼,直接在 XCode 6 上的 Playground 環境下執行,應該都是沒有問題的,如果有問題還請再跟我說。

另外我發現 Logdown 好像不支援 Swift 的語法?所以 Blog 版筆記的程式碼,就不會有 syntax highlight 的效果,晚點我再來做個確認,這時間如果看程式碼不順眼的朋友,建議可以閱讀 Evernote 筆記版,謝謝。

型別 (Type) 基本觀念

  • 不支援隱式型別轉換 (implicit type conversion)
    • 以下程式會失敗 (因為不會自動將 quantity 變數轉為 Double)︰
var quantity = 42;
var unitPrice = 34.55;
var result = quantity * unitPrice;
  • 要做顯示型別轉換才會通過 compile︰
var quantity = 42;
var unitPrice = 34.55;
var result = Double(quantity) * unitPrice;
  • 每個變數在使用前,一定要初始化,否則會 compile error
//var name : String = "";

var name : String;
println("Name: \(name)"); // error!

字串操作

String interpolation

var name = "Jonathan Lin";
println("My name is \(name)");

Switch

  • Switch 下的 case 必須窮舉出來 (switch statement must be exhaustive),可以使用 default case 來滿足這個要求︰
var condition = 0;

switch condition {
case 0:
    println("foo");
case 1:
    println("foo");
default:
    println("foo");
}
  • 可以使用 range operator 來簡化列舉 case 的 effort︰
var condition = 0;

switch condition {
case 0...2:
    println("foo");
default:
    break;
}
  • 真的有 case 不需要做事的話,可以使用 break 離開此 case︰
var condition = 0;

switch condition {
case 0:
    println("foo");
case 1:
    println("foo");
default:
    break;
}
  • Switch 不支援 fall through (No implicit fall through),code required in all cases
    • 以下程式不支援︰
switch condition {
case 0:
case 1:
    println("foo");
}
  • 以這種情形來說,每個 case 都要有自己的邏輯︰
switch condition {
case 0:
    println("foo");
case 1:
    println("foo");
}

Range Operator

  • Closed range operator︰範圍的兩端都在結果序列中
// [0, 1, ...100]

0...100
  • Half-open range operator︰範圍的右邊,不包含在結果序列中
// [0, 1, ...99]

0..<100

Function

  • 預設情形下,function 的輸入參數 (input parameter) 是 constant,不是 variable
    • 以下程式不會被接受,因為 age parameter 是 constant,不是 variable
func myFunction (age : Int) {
    age = 3;
}
  • 如果要指定 function 的 input parameter 為 variable,則要使用 var 來宣告 input parameter︰
func myFunction (var age : Int) {
    age = 3;
}
  • function 參數可以指定預設值,但呼叫時,就要使用 named argument,指定要傳入的參數名稱
func myFunction (age : Int = 99) {
    println (age);
}

myFunction(age: 20)

Array

宣告陣列型別

var numbers : [Int] = [];

Array 支援可變 (mutable) 和不可變 (immutable) 兩種宣告

  • 使用 var 宣告的為可變 Array
var daysInMonth = [31, 28];
daysInMonth[0] = 20;
  • 使用 let 宣告的為不可變 Array。陣列一經宣告後,就不能再修改其中的元素值︰
let daysInMonth = [31, 28];
daysInMonth[0] = 20; // error!
  • 加入新元素在 Array 尾端
var numbers = ["One", "Two"];
numbers.append("Three");
numbers += ["Four"];
  • 插入新元素在 Array 中的特定位置
var numbers = ["One", "Three"];
numbers.insert("Two", atIndex: 1); // result: ["One", "Two", "Three"] 
  • 刪除元素
var numbers = ["One", "Two", "Three"];
numbers.removeAtIndex(1); // result: ["One", "Three"]

numbers.removeLast(); // result: ["One"]

Dictionary

宣告 Dictionary 型別

var products : [Int:String];

新增/更新元素 (Update or insert item)

  • Sample code:
var products : [Int:String] = [1 : "Item1", 2 : "Item2"];
products[3] = "Item3";
products.updateValue("Item4", forKey: 4)
  • updateValue() 會回傳指定 key 中的上一個值 (如果舊的值存在的話),如果沒有舊值,則回傳 nil︰
var products : [Int:String] = [1 : "Item1", 2 : "Item2"];
products.updateValue("Item3", forKey: 3); // return nil

products.updateValue("Item3_v2", forKey: 3); // return {Some "Item3"}

刪除元素

  • 刪除元素,會整個 key/value pair 都自 dictionary 中刪除
var products : [Int:String] = [1 : "Item1", 2 : "Item2", 3 : "Item3"];
products[2] = nil;
products.removeValueForKey(3); // return {Some "Item3"}

巡覽元素

  • 巡覽 dictionary 中的元素︰
var products : [Int:String] = [1 : "Item1", 2 : "Item2", 3 : "Item3"];
for (key, value) in products {
    println("Key=\(key), Value=\(value)");
}

Tuple

  • "A collection of elements"
(1, 2, 3)
("Red", "Blue", "Black")
(1, "Jonathan", 2, "Lin")

將 tuple 做為 function 的回傳型別

  • 簡單用法︰使用 .0.1 來表示 tuple 中的元素 1、元素 2
func getSongAndDuration() -> (String, Int) {
    return ("Song", 30);
}

let result = getSongAndDuration();
println("Song name: \(result.0), duration: \(result.1)");
  • 如果覺得不好用,可以在定義 function 時,以名稱表示回傳 tuple 下,每個元素的別名︰
func getSongAndDuration() -> (name:String, duration:Int) {
    return ("Song", 30);
}

let result = getSongAndDuration();
println("Song name: \(result.name), duration: \(result.duration)");
  • 或是如果 function 沒定義時,可以在呼叫端自己定義別名來方便使用︰
func getSongAndDuration() -> (String, Int) {
    return ("Song", 30);
}

let (name, duration) = getSongAndDuration();
println("Song name: \(name), duration: \(duration)");

Optional

  • 有點像 C# 下的 Nullable
  • 表示這個 variable 可能沒有值
  • forced unwrapping: 在使用 optional 變數時,我們會在變數的後方,加一個 ! 符號,來強迫取出 optional 變數下的值,若此時 optional 變數為 nil,則會產生 runtime error
var temperature : Int? = 3;

// forced unwrapping

if temperature != nil {
    println("The current temperature is \(temperature!).");
}
  • 上述的程式,會印出 "The current temperature is Optional(3).",Optional 會被自動帶出,但這不是我們要的,我們需要使用 if let 來解決此問題︰
var temperature : Int? = 3;

if let result = temperature {
    println("The current temperature is \(result).");
}
  • Optional 可以應用在 dictionary 取值的情形,因為 dictionary 取值時,你無法確定當下 dictionary 是否有值,也是一個 Optional 的情形︰
var products : [Int:String] = [1 : "Item1", 2 : "Item2", 3 : "Item3"];

if let productName = products[2] {
    println("Product name: \(productName)");
} else {
    println("There is no such thing!");
}

列舉 (Enumeration)

  • 列舉可能的值,成為一個新的 data type
enum SeatePreference {
    case Window
    case Middle
    case Aisle
    case NoPreference
}

var jetPrefers : SeatePreference;
jetPrefers = SeatePreference.Window;
jetPrefers = .Middle; // also work!

Closure

  • 和 function 一樣,是一群可 reuse 的 code/logic 集合,function 就是一種 closure
  • 不需要取名
  • 其它語言中,對 Closure 有不同的稱呼,但指涉的概念是相同的︰
    • Anonymous function
    • Lambda
    • Blocks
let myClosure = {
    println("hello world!");
}
  • closure 可以做為參數丟給另一個 function
    • () -() 表示此 closure 的 type 為 不接受任何參數,也不回傳任何值
func performFiveTimes(myClosureParameter: ()->()) {
    for i in 1...5 {
        myClosureParameter()
    }
}

performFiveTimes({
    println("This is simple function.");
});
  • 在定義 closure 時,也可以定義它接收的參數型別,和預計回傳的值型別
{ ()->() in
    println("This is simple function.");
});
  • 使用 closure 在內建的排序 function 上
let unsortedArray = [98, 23, 643, 3, 678, 2, 568, 1, 234, 556, 4];

func compareTwo (first:Int, second:Int) -> Bool {
    return first < second;
}

// explicit function

let sortedArray1 = sorted(unsortedArray, compareTwo);

// closure

let sortedArray2 = sorted(unsortedArray, { (first : Int, second : Int) -> Bool in
    return first < second;
});

Class

  • Swift 在 class 設計上,沒有大家都繼承自 Object 這樣的 default class 的概念

基本

  • 最典型的用例如下︰
class Player {
    // properties

    var name : String = "";
    var score : Int = 0;
    
    // methods

    func description() -> String {
        return("Player \(name) has a score of \(score)");
    }
}

var jonathan : Player = Player();
jonathan.name = "Jonathan";
jonathan.score = 100;
println(jonathan.description());
  • 加上建構子 (initializer)︰
class Player {
    // properties

    var name : String;
    var score : Int;
    
    // methods

    func description() -> String {
        return("Player \(name) has a score of \(score)");
    }
    
    
    init() {
        self.name = "";
        self.score = 0;
    }
    
    init(name : String) {
        self.name = name;
        self.score = 0;
    }
}

var jonathan : Player = Player();
jonathan.name = "Jonathan";
jonathan.score = 100;
println(jonathan.description());

var secondPlayer : Player = Player(name: "Bill");
secondPlayer.score = 100;
println(secondPlayer.description());
  • 解構子 (de-initializer))
    • 解構子一般不用我們自己呼叫
    • Swift 使用 automatic reference count (ARC),會自動做記憶體管理 & 呼叫 deinit()
class Player {
    // properties

    var name : String;
    var score : Int;
    
    // methods

    func description() -> String {
        return("Player \(name) has a score of \(score)");
    }
    
    
    init() {
        self.name = "";
        self.score = 0;
    }
    
    deinit {
        // do something

    }
}

繼承

  • 術語︰super class & sub class => 傳統的 parent class & child class
  • Initializer 要覆載時,需使用 override keyword
class PermierPlayer : Player {
    var memberLevel : String;
    
    override init() {
        memberLevel = "Gold";
        super.init(); // required for initializer

    }
    
    func calculateBonus() {
        self.score += 100;
    }
    
    // methods

    override func description() -> String {
        let originalMessage = super.description();
        return ("\(originalMessage) and is a \(memberLevel) member");
    }
}
  • 如果 super class 的成員不想被 override,可以加上 final keyword
class Player {
    // ...        

    final func description() -> String {
        return("Player \(name) has a score of \(score)");
    }        
    // ...

}

Computed property

  • 和 C# 類似,你可以針對 property 指定它的 getter/setter
class Person {
    // STORED properties

    var firstName : String;
    var lastName : String;
    
    // COMPUTED properties

    var fullName : String {
        get {
            return firstName + " " + lastName;
        }
    }
    
    init(first : String, last : String) {
        self.firstName = first;
        self.lastName = last;
    }
}

var examplePerson = Person(first: "Jonathan", last: "Lin");
println(examplePerson.fullName);
  • 如果 computed property 只有 getter 沒有 setter,則程式碼可以進一步簡化︰
class Person {
    // STORED properties

    var firstName : String;
    var lastName : String;
    
    // COMPUTED properties

    var fullName : String {
        return firstName + " " + lastName;
    }
    
    init(first : String, last : String) {
        self.firstName = first;
        self.lastName = last;
    }
}

var examplePerson = Person(first: "Jonathan", last: "Lin");
println(examplePerson.fullName);

Type property / method

  • 觀念上有點像 Java/C# 中提到的 static field / method
  • 在變數宣告前,加上 class keyword
class BankAccount {
    class var interestRate : Float {
        return 2.0;
    }
    
    class func example() -> String {
        return "for example.";
    }
}

var interestRate = BankAccount.interestRate;

Lazy property

  • property 的值將等到它第一次被需要時,才會做取值的動作
  • 使用 lazy keyword
import UIKit;

func getBonus() -> Int {
    return random();
}

class Player {
    var name = "John";
    lazy var bonus = getBonus();
}

var firstPlayer = Player(); // # firstPlayer.bonus = nil;

println("\(firstPlayer.bonus)");

Property observer

  • 在 property 中,可以加上 willSetdidSet 等 observer,在 property 新值被設定的前後,可以執行指定的動作
  • Property observer 只會在物件被初始化後才會失效 (也就是建立物件後的下一個 statement 開始作用)
import UIKit;

func getDailyBonus() -> Int {
    return random();
}

class Player {
    var name : String = "John" {
        willSet {
            println("About to change name to \(newValue)");
        }
        didSet {
            println("Have changed name from \(oldValue)");
        }
    }
    lazy var bonus = getDailyBonus();
}

var firstPlayer = Player();
firstPlayer.name = "Jonathan";

Access modifiers

  • 如下︰

Structure

  • Swift 下的 Structure 可以擁有和 class 一樣的成員。ex: method、computed property、observer……等。
  • 宣告方式和 class 基本上差不多︰
struct SimpleStructure {
    var value : Int = 99;
}

var foo = SimpleStructure();
foo.value;
  • Swift 下的內建型別,如 String、Bool、Float、Array、Dictionary 底層都是以 Structure 來實作,而不是 class
  • Structure 與 Class 的最大差異,在於 Structure 是 Value Type,而 Class 是 Reference Type
  • Structure 無法使用繼承機制

針對運算子 (operator) 的補充

equality 運算子 vs. identity 運算子

  • equality 運算子︰比較運算子前後的 2 個運算元的值是否相等
    • == equal to
    • != not equal to
  • identity 運算子︰比較運算子前後的 2 個運算元,其 reference 指向的物件是否相同。因此 identity 運算子,無法用於 value type 的資料型別上 (ex: String、Array 等)
    • === identical to
    • !== not equal to
import UIKit;

var dateA = NSDateComponents();
dateA.year = 2000;
dateA.month = 1;
dateA.day = 1;

var dateB = NSDateComponents();
dateB.year = 2000;
dateB.month = 1;
dateB.day = 1;

// check equality

if (dateA == dateB) {
    println("dateA and dateB are equal to each other");
}

// check identity

if (dateA === dateB) {
    println("dateA and dateB are identical");
}

Nil coalescing operator (??)

  • 基本語法︰OptionalValue ?? DefaultValue
var personalSite : String?;
let defaultSite = "http://www.google.com";

var webSite = personalSite ?? defaultSite;

Remainder operator (%)

  • Swift 下的 % 運算子,不同於其他語言的地方是,可以用在浮點數和負數上
var totalUnits = 12232.45;
var unitsPerBox = 25.2;

var leftover = totalUnits % unitsPerBox;

匯入 Framework & 與 Objective-C class 互動

  • 內建 Framework 目錄︰/System/Library/Frameworks
  • 其中 Foundation Framework 是每個 Mac/iOS 下各個 Framework 的基礎建設
import Foundation;

var foo = "Hello world!";
foo.uppercaseString; // HELLO WORLD!";


let someDate = NSDate();

let someURL = NSURL(string: "http://www.google.com");
someURL?.host;

型別確認 (checking) 與向下轉型 (downcasting)

  • 可以使用 is 關鍵字 ,來確認某個 object 是否為指定的型別
  • 可以使用 as 關鍵字,來將某個 object 的型別,轉換為子型別。如果轉換失敗,則會產生 runtime error
  • 可以使用 as? 關鍵字,來將某個 object 的型別,轉換為子型別。如果轉換失敗,則回傳 nil
import UIKit;

let myButton = UIButton();
let mySlider = UISlider();
let myTextField = UITextField();
let myDatePicker = UIDatePicker();

let controls = [myButton, mySlider, myTextField, myDatePicker];
for item in controls {
    // optiona 1: check type with "is"

    if item is UIDatePicker {
        println("We have a UIDatePicker");
        // downcast with "as"

        let picker = item as UIDatePicker;
        picker.datePickerMode = UIDatePickerMode.CountDownTimer;
    }
    
    // option2: try to downcast with "as?"

    let picker2 = item as? UIDatePicker;
    if (picker2 != nil) {
        picker2!.datePickerMode = UIDatePickerMode.CountDownTimer;
    }
    
    // option3: combine

    if let picker3 = item as? UIDatePicker {
        picker3.datePickerMode = UIDatePickerMode.CountDownTimer;
    }
}

AnyObject & Any reference

  • 使用 AnyObject 與 Any 型別的主要原因,是希望可以和原有的 Objective-C 的型別系統相容
  • AnyObject: 任何 object type
  • Any: 任何型別的變數都可以,不限制一定要是 object type。ex: Structure、Closure、Tuple
  • Objective-C id 和 Swift 下的 AnyObject 是等價的
  • 如果我們 import 了 UIKit framework,則 String、Int 等型別會被視為 Object 看待,所以可以被放入 AnyObject 變數中
import UIKit; // required


var foo : AnyObject = "test";
var boo : AnyObject = 3;
  • 使用 is、as、as? 來做型別確認和向下轉型,確認系統安全與不會拋出 runtime error
import UIKit;

var foo : AnyObject = "This is a simple message";

if (foo is String) {
    let wordsArray = foo.componentsSeparatedByString(" ");
}

Protocol

  • 定位和其他語言中的介面 (interface) 很像
import UIKit;

protocol ExampleProtocol {
    func simpleMethod() -> Bool;    
    var simpleProperty : Int { get set }
}

class MyClass : ExampleProtocol {
    var simpleProperty : Int {
        get {
            return 55;
        }
        set {
        }
    }
    
    func simpleMethod() -> Bool {
        return false;
    }
}

Extension

  • Add methods and properties to existing types.
import UIKit;

extension String {
    func myOwnUpperString() -> String {
        return self.uppercaseString;
    }
}

var message = "Hello world";
message.myOwnUpperString();

泛型 (Generics)

  • 和其他語言中的泛型機制類似。
import UIKit;

func displayArray<T>(theArray : [T]) {
    for eachitem in theArray {
        print(eachitem);
        print(" : ");
    }
    println();
}

let myInts = [1, 2, 3];
let myStrings = ["hello", "world"]
displayArray(myInts)
displayArray(myStrings)

附錄︰XCode Hotkey 筆記

  • option + click on variable => 顯示變數型別