Search

iOS - Swift 3 基礎語法介紹

2016-12-29 12:18 PM

此篇文章將介紹在 Apple iOS 平台開發使用的 Swift 3 基礎語法

在某些地方會使用 Java 的類似方法做比對

如此一來便能夠在某些程度上加速對 Apple iOS Swift 3 基礎語法上的了解(當然前提是你懂 Java 囉~)

// 若不使用 ; 結尾也沒問題 swift 使用斷行符號結尾

// 特性 1
// Swift 內的所有物件 除了 class 類別物件以外
// 其他物件全部都是 pass by value (struct, enum)
// 也就是說 在作為參數傳遞、指定值的時候 除了 class 類別物件以外
// 其他物件都會複製一份自己 此時若更改複製後的屬性值 將不會變更原本物件的屬性值
// array, dictionary, String, Int, Double 等等都是屬於 struct 的類別

// 特性 2
// struct/enum 若被指定給 constant 變數(let a = someStruct)
// 則此 struct/enum 將會變為 immutable 屬性值皆不可異動
// 而在作為參數傳遞時的所有變數都將會是常數( func perform( name:String, value:Int) name 及 value 都是常數 不可異動 )
// 此時若有可能會在方法內修改 struct/enum 的屬性值時 需要在 func 前方加入 mutating 保留字
// 即可異動 struct/enum 的屬性值(詳見 protocol)

// 特性 3
// Swift 會猜測變數型態 因此若無特殊需求 不需要在宣告變數時加入型態

// 印出字串

print("Hello, world!");

--

// 宣告變數

var name:Type = value;
var name1, name2, name3: Type

--

// 宣告常數

let name:Type = value;

--

// 宣告 optional

var name:Type? = nil;

// 宣告為 optional 可以將變數值設定為 nil
// 並可使用於 if 條件 判定是否為 nil 值(詳見 if 邏輯判斷式)
// 只有 optional 變數可以使用是否為 nil 的判斷

--

// 類型轉換 

var floatNum:Float = 2.353;
var intNum:Int = Int(floatNum);

// print(intNum); -> 2

var string:String = "There are \(intNum) pens.";

// print(string); -> There are 2 pens.

--

// 宣告陣列

var shoppingList = ["catfish", "water", "tulips", "blue paint"];

// 指定陣列值(若超過目前陣列長度將出現 indexOutOfBound 錯誤)

shoppingList[0] = "QQ";

// 新增陣列值

shoppingList.append("TT");
shoppingList += ["AA", "BB"];

// 插入陣列值

shoppingList.insert("CC", at: 2);

// 建立空陣列

var emptyArray:[String] = [String]();
var emptyArray:[String] = [];

--

// 宣告 dictionary

var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]

// 指定 dictionary 值(若不存在則新增至 dictionary)

occupations["QQ"] = "QQ";

// 建立空 dictionary

var emptyDictionary:[String: Float] = [String: Float]();
var emptyDictionary:[String: Float] = [:];

--

// Tuple
// 新型態與語法 可快速建立可儲存多個值的多個屬性
// 在 func 語法內有進階應用方式(return 值)

let x: (String, Int, Double) = ("Hello", 2, 0.85)
// set tuple name
let (word, number, value) = x

print(word)     // Hello
print(number)   // 2
print(value)    // 0.85

// with default name
let x:( word: String, number: Int, value: Double) = ("Hello", 2, 0.85)

print(x.word)     // Hello
print(x.number)   // 2
print(x.value)    // 0.85

// rename tuple
let (w, n, v) = x

print(w)   // Hello
print(n)   // 2
print(v)   // 0.85

--

// Range
// 儲存開始與結束 Index 的泛型物件

struct Range<T>{
    var startIndex: T
    var endIndex: T
}

// 且具簡易表示語法 ... 以及 ..<

// 範例: 取得陣列中的 subArray
let array = ["a", "b", "c", "d"]
var subArray1 = array[2...3] // c, d
var subArray2 = array[2..<3] // c

for i in 27...104 {} // Range 可以列舉(詳見 for 迴圈)

--

// 邏輯判斷式

var big:Int = 3;
var small:Int = 1;

if( big > small ){
    print("Yes");
}
else{
    print("No")
}

// print -> Yes

// optional 應用

var optionalString: String? = "Hello"
print(optionalString == nil)

var optionalName: String? = "John Appleseed"
var greeting = "Hello!"

// 若 optionalName 是 nil 則不會進入
if let name = optionalName {
    greeting = "Hello, \(name)"
}
else{
    greeting = "Hello, nobody"
}

// switch
// 與 java 不同的是 不需使用 break; 中斷 case 區塊
// 因為 swift 進入 case 後就不會再繼續往其他 case 執行了

let vegetable = "red pepper"
switch vegetable {
case "celery":
    print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
    print("That would make a good tea sandwich.")
// 將 vegetable 值指定給 x 並判斷 x 是否以 "pepper" 結尾
// 若此處直接使用 vegetable.hasSuffix 會出錯
// 用 let 將 vegetable 值指定給 x 常數後即可使用 switch 判斷
case let x where x.hasSuffix("pepper"):
    print("Is it a spicy \(x)?")
default:
    print("Everything tastes good in soup.")
}

// print -> Is it a spicy red pepper?

--

// 若值不存在 則給予初始值的方法

let nickName: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickName ?? fullName)"

// print(informalGreeting); -> Hi John Appleseed

--

// 基礎 for 迴圈

var total = 0

// 等同於 for( int i = 0; i < 4; i++ )
for i in 0..<4 {
    total += i
}

print(total);

// print -> 6

// 列出成員 for 迴圈

let interestingNumbers = [
    "Prime": [2, 3, 5, 7, 11, 13],
    "Fibonacci": [1, 1, 2, 3, 5, 8],
    "Square": [1, 4, 9, 16, 25],
]
var largest = 0

// kind 為 key 值, numbers 則是 value 值
// 變數名稱自訂 dictionary 本身無排序
for (kind, numbers) in interestingNumbers {
    // 使用 in 依序列出陣列成魚
    for number in numbers {
        if number > largest {
            largest = number
        }
    }
}
print(largest)

// print -> 25

// while 迴圈
// 條件成立才會進入區塊

var n = 2
while n < 100 {
    n = n * 2
}
print(n)

// print -> 128

// repeat while 迴圈
// 至少執行一次 條件成立才會執行下一次

var m = 2
repeat {
    m = m * 2
} while m < 100

print(m)

// print -> 128

--

// 宣告方法

// func 保留字 
// greet 自訂方法名稱
// person: String 變數名稱及類型
// -> String 方法回傳值類型 若無回傳值則不需加入
func greet(person: String, day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")

// 不需回傳值的方法
func greet() {
    print("Testing");
}
greet();

// 使用自訂變數標籤(Label)
// 預設將會使用變數名稱作為標籤
// 若要自訂可在參數名稱前空一格後加入 或是使用 _ 表示不需使用標籤

func greet(_ person: String, on day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")

// 回傳 不定數量的多個數值(tuple)

func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
    var min = scores[0]
    var max = scores[0]
    var sum = 0
    
    for score in scores {
        if score > max {
            max = score
        } else if score < min {
            min = score
        }
        sum += score
    }
    
    return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
// 可使用名稱取值
print(statistics.sum) // 120
// 或是使用索引值取值
print(statistics.2) // 120


// 傳入不定數量的多個數值

func sumOf(numbers: Int...) -> Int {
    var sum = 0
    for number in numbers {
        sum += number
    }
    return sum
}
sumOf() // 0 
sumOf(numbers: 42, 597, 12) // 651

// 巢狀方法
// 可以在方法內再宣告一個方法

func returnFifteen() -> Int {
    var y = 10
    func add() {
        y += 5
    }
    add()
    return y
}
returnFifteen() // 15

// 使用其他方法作為方法的參數值

func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
    for item in list {
        if condition(item) {
            return true
        }
    }
    return false
}
func lessThanTen(number: Int) -> Bool {
    return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)

// 傳入使用匿名方法參數
// 匿名方法不需宣告名稱及加入 func 保留字
// 僅需定義參數及回傳值即可 但後方需使用 in 保留字作為區塊宣告(相對於一般方法使用的 {})

// map 為傳入方法參數 自訂轉換功能(例如轉換大小寫、更改數值) 回傳轉換後的陣列(原始陣列值不變)

// 所有數值 * 3
var numbers = [20, 19, 7, 12]

numbers.map({
    (number: Int) -> Int in
    let result = 3 * number
    return result
})

// 更簡易的寫法(由於參數類型已知 回傳類型亦同 因此一併忽略)
let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)

// 所有字串改為大寫
var strings = ["a", "b", "c"];

strings.map({
    (string:String) -> String in
    return s.uppercased()
})

// 更簡易的寫法(由於參數類型已知 回傳類型亦同 因此一併忽略)
strings.map({ $0.uppercased(); })

// 亦可用於排序方面
let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)

// 或是篩選陣列中的值 並將所有回傳 true 的值重新組成一個新的陣列
let array = [2, 7, 15, 36, 87, 104]
print( array.filter({ $0 > 20 }) ) // [36, 87, 104]

// 也可以將 array 內的值做一個運算
let array = [1,2,3,4,5]
print( array.reduce(0){ $0 + $1} ) // 15

--

// 宣告 class

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

// 具有建構子的 class
// 建構子為不需加入 func 保留字也不需宣告回傳值類型的方法 名稱必須為 init
// 建立物件時必須傳入參數 var ns = NamedShape(name: "PiTT");
// 若加入 deinit 則是當物件要從記憶體內移除時自動被呼叫的方法
// 可以在此處 release 某些物件

class NamedShape {
    var numberOfSides: Int = 0
    var name: String
    
    // 建構子
    init(name: String) {
        self.name = name
    }
    
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

// 若使用 init? 則表示初始化過程可能會回傳 nil
// 也就是回傳一個 Optional 物件

class NamedShape{
    var numberOfSides: Int = 0
    var name: String
    
    // 建構子
    init?(name: String) {
        if( name.isEmpty ) return nil
        self.name = name
    }
    
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

// 繼承 class
// 在後方加入父類別 class

class Square: NamedShape {
    // 使用 override 保留字表示此為覆寫父類別方法的方法
    // 若未加入 override 保留字 同時方法名稱、傳入參數及回傳直接相同的話
    // IDE 會直接報錯
    override func simpleDescription() -> String {
        return "A square with sides of length \(sideLength)."
    }
}

// 使用欄位的 getter/setter

class Square: NamedShape {
    var sideLength: Double = 3.0;

    var perimeter: Double {
        get {
            return 3.0 * sideLength
        }
        set {
            // newValue 表示傳入的參數(等號後方的值)
            // set 本身已隱含此預設名稱 set(newValue)
            // 亦可自定義名稱 使用 set(value)
            sideLength = newValue / 3.0
        }
    }
}

var s: Square = Square();

// print(s.perimeter) -> 9.0

// 使用 willSet
// willSet 表示當欄位值在 init 之外改變值的時候都會呼叫的方法
// 例如以下範例為保證兩個欄位值的邊長皆相同的方法

class TriangleAndSquare {
    var triangle: EquilateralTriangle {
        willSet {
            square.sideLength = newValue.sideLength
        }
    }
    var square: Square {
        willSet {
            triangle.sideLength = newValue.sideLength
        }
    }
    init(size: Double, name: String) {
        // 此處設定欄位值並不會觸發 willSet
        // 所以若修改為 square = Square(sideLength: size * 2, name: name)
        // 則兩個欄位的邊長將會不相等
        square = Square(sideLength: size, name: name)
        triangle = EquilateralTriangle(sideLength: size, name: name)
    }
}

var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength) // 10
print(triangleAndSquare.triangle.sideLength) // 10
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.square.sideLength) // 50
print(triangleAndSquare.triangle.sideLength) // 50

// 使用 optional 物件

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
// optional 物件使用時必須加入 ? 若物件本身為 nil 則後方表達示將會直接忽略並直接回傳 nil
let sideLength = optionalSquare?.sideLength

--

// enum

// 若使用 Int 類型則 enum 的 RawValue 會預設由 0 開始自動遞增
// 其他類型則必須指定各元素的 RawValue
enum Rank: Int {
    // 若第一個元素設定為 1 則會從 1 開始遞增
    case ace = 1
    case two, three, four, five, six, seven, eight, nine, ten
    case jack, queen, king
    // 亦可宣告方法在 enum 內
    func simpleDescription() -> String {
        switch self {
        // 注意此處需在前方加入 . 符號
        // 因為是在 switch self 的 self 為 Rank 物件 所以下方的 case 可以直接使用 .ace 取得值
        // 若在 switch 的目標不是 enum 物件本身(或其元素) 則必須使用 Rank.ace
        case .ace:
            return "ace"
        case .jack:
            return "jack"
        case .queen:
            return "queen"
        case .king:
            return "king"
        default:
            return String(self.rawValue)
        }
    }
}
let ace = Rank.ace
let aceRawValue = ace.rawValue
print( ace.simpleDescription() ); // 字串結果的 ace

// 亦可使用以下方式取得 enum 元素

Rank(rawValue: 3)

// 搭配前面學到的 optional 方法可避免 rawValue 不存在的情況

if let convertedRank = Rank(rawValue: 3) {
    let threeDescription = convertedRank.simpleDescription()
}

// 進階應用
// 使用 enum 搭配 struct 簡化程式碼

var result: Double = 0.0

enum Operation{
    // 可傳遞 associate value 以便之後取得 需注意此參數與 rawValue 是不同的意義 類似於 optional 的 associate value
    case Constant(Double)
    case UnaryOperation
}

var operations: Dictionary<String, Operations> = [
    "pi" : .Constant(M_PI)
    "e" : .Constant(M_E)
]

func performOperation(symbol: String){
    if( let operation == operations[symbol] ){
        switch operation {
            // 使用 let value 取得 associate value
            case .Constant(let value): 
                result = value;
            default: break;
        }
    }
}

--

// Struct
// 與 class 非常類似 亦可包含方法與欄位 僅可繼承 Protocal
// 但在作為參數傳遞時 struct 將會複製一份自己作為傳輸 而 class 則是僅傳遞記憶體位置
// 因此 struct 傳遞後會有多個物件存在於不同記憶體位置
// 而 class 則只會有一個記憶體位置

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
}

--

// Protocal
// 類似於 Java 的 Interface

protocol ExampleProtocol {
    var simpleDescription: String { get }
    // mutating 表示此方法可能會在方法內修改 struct/enum 的屬性值
    mutating func adjust()
}

class SimpleClass: ExampleProtocol {
    var simpleDescription: String = "A very simple class."
    var anotherProperty: Int = 69105
    // 此處覆寫的方法前方不需加入 mutating 保留字 因為 class 本身原本就可以覆寫自己的屬性值
    func adjust() {
        simpleDescription += "  Now 100% adjusted."
    }
}

struct SimpleStructure: ExampleProtocol {
    var simpleDescription: String = "A simple structure"
    // Struct 則必須加上 mutating 保留字 以便覆寫屬性值
    mutating func adjust() {
        simpleDescription += " (adjusted)"
    }
}

--

// Extension
// 可以擴充原本存在的 Class 功能

extension Int: ExampleProtocol {
    var simpleDescription: String {
        return "The number \(self)"
    }
    mutating func adjust() {
        self += 42
    }
}

print(7.simpleDescription) // The number 7

--

// 多型
// 與 Java 相同 亦可直接將變數類型指定為 Protocal
// 可使用的方法只有 Protocal 內定義的方法

let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)

--

// 錯誤處理

// 直接繼承 Error Protocal
enum PrinterError: Error {
    case outOfPaper
    case noToner
    case onFire
}

// 在會拋出異常的方法參數後方加入 throws 保留字 表示此方法會拋出異常
func send(job: Int, toPrinter printerName: String) throws -> String {
    if printerName == "Never Has Toner" {
        throw PrinterError.noToner
    }
    return "Job sent"
}

// 使用 do-catch 控制會拋出異常的流程
// 須注意使用時在會拋出的方法前方需加入 try 保留字
do {
    let printerResponse = try send(job: 1040, toPrinter: "Never Has Toner")
    print(printerResponse)
} 
catch {
    print(error)
}

// 亦可使用多個 catch 處理不同的異常類型

do {
    let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
    print(printerResponse)
} 
// 處理 PrinterError.onFire 這項錯誤
catch PrinterError.onFire {
    print("I'll just put this over here, with the rest of the fire.")
} 
// 處理所有 PrinterError 類型的錯誤
catch let printerError as PrinterError {
    print("Printer error: \(printerError).")
} 
// 處理其他所有錯誤
catch {
    print(error)
}

// 或是使用 optional 方式處理異常
// 會拋出異常的方法將回傳 nil

let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner") // nil

--

// defer
// 類似於 Java 的 finally
// 會在 function 的所有內容執行完成後進入

var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
 
func fridgeContains(_ food: String) -> Bool {
    fridgeIsOpen = true
    defer {
        fridgeIsOpen = false
    }
    
    let result = fridgeContent.contains(food)
    return result
}

fridgeContains("banana")
print(fridgeIsOpen) // false

--

// 泛型

func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
    var result = [Item]()
    // 此處使用 _ ,因為實際是不需使用到此變數 所以使用 _ 直接忽略變數宣告 類似於參數傳遞的 _ 標籤
    for _ in 0..<numberOfTimes {
        result.append(item)
    }
    return result
}
makeArray(repeating: "knock", numberOfTimes:4) // ["knock", "knock", "knock", "knock"]

--

// where
// 可加在 function 後方表示僅有符合此條件者才可進入 function 內
// 例如 在定義幾乎完全相同的 function 時( 如名稱、回傳類型相同 且參數皆為泛型時 )
// 在 Java 內會出現無法識別方法的編譯錯誤
// 而 Swift 則可以使用 where 制定不同泛型類型對應的不同處理方法

// Sequence 表示陣列類型的父類別
// where 表示 T, U 皆需實作 == 比對的方法(Equatable) 且兩者內容元素的類型相同才可進入此方法
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Int
    where T.Iterator.Element: Equatable, T.Iterator.Element == U.Iterator.Element {
        for lhsItem in lhs {
            for rhsItem in rhs {
                if lhsItem == rhsItem {
                    return 0
                }
            }
        }
        return 1
}

// 回傳為 Int 類型的相同泛型參數方法
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Int {
    return 2
}

// 回傳為 Bool 類型的相同泛型參數方法
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool {
    return false
}

// 以下三者將各自呼叫不同的方法
anyCommonElements([1, 2, 3], [3])   // 0
var a:Int = anyCommonElements([1, 2, 3], ["String"]) // 2
var b:Bool = anyCommonElements([1, 2, 3], ["String"]) // false

--

// 執行緒

let queue:OperationQueue = OperationQueue();
        
queue.addOperation {
    // Code
}

--

// lazy 保留字
// 會在真正使用到此變數時才進行初始化動作(僅能使用在 var 變數 不可使用在 let 常數)

lazy var calculator = Calculator() // not initialized yet

// 若要在給訂初始值時使用到 self 物件 可以使用 lazy 保留字
// 若未加入 lazy 則會出現 self 尚未被初始化錯誤

lazy var t: Type = {
    var r = self.someProperty * 2
    return r
}

--

// AnyObject
// 如字面意義 代表任何物件型態

var any:AnyObject;

// 是否為 SomeObjecy 類型
if any is SomeObject{

}

// 將值指定給 foo 前確認型態(Casting)
if let foo = any as? SomeObject{

}

// Assert
// Debug 時使用 在 release 時會自動忽略

// autoclosure 使用時不需加入 {}
assert( () -> Bool, "message")

assert( validate(), "Validate failed" )

各項資料連結
Swift Tour