此篇文章將介紹在 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" )