簡單記錄 Apple 官方 Swift Guide 的重點與心得。
(撰於 2017-02-06,基於 Swift 3.1)
Declaration
宣告變數使用 var
,宣告常數使用 let
- 使用
var
宣告,該值為 mutable - 使用
let
宣告,該值為 immutable
let myConst = "constant"
var myVar = 1234
myVar = 5678
慣例是都先使用
let
宣告,等到之後需求或 compiler 報錯時,再修正為 mutable 的var
Type Inference
自動透過賦予的值推斷型別,也可以顯式聲明型別。
let doubleValue = 70.0 // Double type
let myStr: String
myStr = "1234"
Type Safety
Swift 是一個非常嚴謹的語言,注重型別安全(Type Safety)
- 宣告常數、變數時必須賦值或聲明顯示型別
- 常數、變數使用前必須給定初始值
- 型別無法任意轉換,必須顯式指定型別轉換。
Fundamental data type
Swift Standard Library 定義了許多基本型別:
Integer/Unsigned Integer, Double, Float
let myInt = 1234 // integer
let myInt8: Int8 = 1234 // 8-bit integer
let myUInt: UInt = 1234 // unsigned integer
let myDouble = 1234.0 // double
let anotherDouble: Double = 123 // double
let myFloat: Float = 123.0
Boolean
布林值不再用 YES
NO
,改為 true
false
let myBoolean = true
let myFalse: Bool = false
String
是字元集合(但非 Array 這種 collection type)
let myStr = "hello, world"
let anotherStr: String = "hello, swift"
print(myStr.hasPrefix('hello'))
// true
Swift 字串不僅是簡單的字元集合,有許多針對 Unicode support 與 橋接 NSString 的設計,但由於不太方便調用,預計 Swift 4 會大幅修正
Array/Dictionary
最常用的兩種 collection type,字面量使用 []
將值包裹,Dictionary
使用 key: value
表示。
var array = [1, 2, 3, 4]
print(array[2]) // 2
array[2] = 3
print(array[2]) // 3
var dict = ["A": 1, "B": 2]
print(dict["A"]) // 1
dict["A"] = 5
print(dict["A"]) // 5
除了這些基本型別,
import Foundation
後也可以使用諸如NSString
、NSArray
這類 Foundation Object。不過 Swift 與 Objective-C 橋接做得很好,建議能用 Stdlib 解決就不要用 Foundation Object。
Operator
Swift 的 運算子大多都與 C/Objective-C 雷同。 值得一提的是,所有運算子都是在 standard library 中以 function 的形式宣告。
i++
++i
這種 prefix/postfix increment 在 Swift 3 之後完全移除了
===
operator
Swift 中新增的運算子,用來比較 reference type 指向的實例(比較 memory address)是否相同。功能等同 Objective-C 的 [NSObject isEqual:]
。
Type checking/casting
- is:檢查實例是否為特定子類別
- as:將實例轉型為其他相關子類別
- as?:轉型失敗回傳 nil
- as!:強迫轉型(不建議使用)
- as 也可以和 value-binding 搭配服用
let myInt: Int = 10
myInt is Int // true
myInt as? String // nil
// value-binding 搶先看 -----------
func testType(_ variable: Any) {
switch variable {
case let variable as Double:
print("Double \(variable)")
case let variable as Int:
print("Int \(variable)")
case let variable as String:
print("String \(variable)")
default:
print("Unknown Type")
}
}
let a: Any = 10
let b: Any = String(10)
let c: Array = [1, 2, 3, 4]
testType(a)
testType(b)
testType(c)
// Int 10
// String 10
// Unknown Type
Control Flow
與 C/Objective-C 的共通點:
- 大多數寫法類似
- statements 內宣告的變數生命週期只在該 code block 內,不污染 outer scope
if/else
- condition 可省略
()
(主流 coding style 會省略) - 無論 statement 有幾行,都一定要用
{}
包裹起來。
let a = 6
if (a > 5) print(a) // compile error
if a > 5 { print(a) }
if a > 5 {
// true go here
} else {
// false go here
}
for
- 使用
for ... in
的寫法 - condition 不可以加上
()
- 無論 statement 有幾行,都一定要用
{}
包裹起來。
let abcd = ["A", "B", "C", "D"]
for i in abcd {
print(i)
}
Swift 3 後,無法使用傳統 C style for loop for (int i = 0; i < 5; i++)
,若需取得 index,可以:
- 使用
enumerated
方法。 - 使用
Range Operator
產生Range
Object (不推薦,容易 out of range)
let abcd = ["A", "B", "C", "D"]
for (idx, val) in abcd.enumerated() {
print(idx, val)
}
for val in 0..<abcd.count {
print(val)
}
while/repeat-while
- condition 可省略
()
(主流 coding style 會省略) - 無論 statement 有幾行,都一定要用
{}
包裹起來。
repeat...while
等同於 C 的do...while
var i = 0
while i < 5 {
print(i)
i += 1
}
i = 0
repeat {
print(i)
i += 1
} while i < 5
switch
- condition 可省略
()
(主流 coding style 會省略) - 可以比對許多不同的型別,不限制於 character 或 integer
- 預設每個 case 會自動 break,不需加上
break
- case statements 不需要加上
{}
- 如不加
default
case,需枚舉所有 case,否則 compiler 會 murmur - 如需 C style 的 statements fall through,請加
fallthrough
- 配合 pattern matching 可以玩很多花樣
let number = "A"
switch aString {
case "A":
print("Got")
case "B":
print("Got B")
fallthrough
default:
print("Got B with fallthrough")
}
Pattern matching 搶先看(with value-binding)
let point = (1, 0)
switch point {
case (_, 0):
print("on x axis")
case (0, _):
print("on y axis")
case (0, 0):
print("at origin")
case let (x, y):
print("point at (\(x), \(y))")
}
guard-else
- 用途:檢查是否符合 requirement,用於 Early Exit(作用類似
if (!...) return
) - guard 的 condition 為 requirement,與
if
相反 - 須與
else
搭配,該else
內必須有轉換 control flow 的動作,如break
、return
等 - 與 optional binding 搭配,可以節省寶貴的 indentation
- 慣例寫在該 code block 的最頂層
let aNil: Int? = 5
guard true else {
fatalError("you won't fail here")
}
// optional-binding
guard let integer = aNil else {
fatalError("you won't fail here")
}
print(integer) // no need to wrap the optional
guard int < 5 else {
fatalError("you failed")
}
由於
guard
關鍵字用在 Early exit,else
clause 裡不應有太複雜的邏輯
defer
- 用途:在該 code block 返回之前,執行
defer
的動作 - 若有多個
defer
,以相反方向執行(由下而上) - 類似其他語言 try-catch-finally 的 finally
- 慣例寫在該 code block 的最頂層
func f() {
defer { print("Last") }
defer { print("Third") }
defer { print("Second") }
print("First")
}
個人認為
defer
本身執行順序相反,比較不推薦使用多個 defer,會減少 readability
Type system
Swift 除了 builtin types 以外,還有許多方式可以宣告 custom type,介紹常見的幾種
- value types
- enum
- struct
- reference types
- class
- function
- others
- tuple
- protocol
- extension
Swift 中,struct、enum、function 皆與 class 一樣是 first-class citizen,可以賦值、 作為 function 參數或 function 的返回值。
Value Type V.S. Reference Type
Swift 的世界中,把 type 分為 value type 與 reference type,和 Objective-C 萬物皆繼承 NSObject
很不一樣。
Behavior | Value Type | Reference Type |
---|---|---|
copy | 複製一份 copy | 分享同一份 value(複製新的reference) |
function call | call by value | call by reference |
修改屬性的method | inhibited (func 需用 mutating 修飾) | allowed |
簡而言之 value type 賦值到新的 variable 時,會複製整個實例;reference type 則只會指向同一個實例。
如果 value type 的 method 需要改變自己的 property 時,該 method 需要使用 mutating
修飾。
// Value Type ----------
let array1 = [1, 2, 3]
var array2 = array1
array2[0] = 4
print(array1) // still the same value
// [1, 2, 3]
print(array2) // copied and modified
// [4, 2, 3]
// Reference Type -------
class MyClass {
var prop = 1
}
let instance1 = MyClass()
var instance2 = instance1 // a new reference to instance1
instance2.prop = 2
print(instance1)
// 2
print(instance2)
// 2
print(instance1 === instance2)
// true
慣例上,在需求允許的情況,推薦使用 value type(struct、enum)創建新的型別,在 assign/copy 時不會造成太多 side effect。例如在與 database 溝通等 object mapping 的情境下。
小提醒:Stdlib 的 Array/Dictionary 皆為 value type
Class
Declaration
class 的宣告方式和大多數程式語言相似
class MyClass {
// ...
}
繼承其他 class 或 protocol 則在 classname 後加上 : MySuperClass
class MySubClass: MyClass {
// ...
}
Swift 的 class 依然不允許多重繼承,但可以繼承多個 protocol,以 ,
分隔不同的繼承來源。
class AnotherSubClass: MyClass, MyProtocol, AnotherProtocol {
// ...
}
Properties
class 的 property 分為兩種
- Stored Property
- Computed Property
Stored Property
getter/setter 就是 property 本身
class Rect {
var width = 10
var height = 20
}
let rect = Rect()
rect.width = 20
print(rect.width)
Computed Property
- getter/setter 透過計算得來
- setter 透過
newValue
變數取得新值 - 只有 getter 時,可以直接 return 該值(Objective-C readonly property)
class Rect {
var width = 10
var height = 20
var x = 0
var y = 0
var centerX: Int {
get {
return x + width / 2
}
set {
x = newValue - width / 2
}
}
var area: Int { // readonly
return width * height
}
}
Property Observers
- property willSet/didSet 時做對應的動作
- 可取代部分 Objective-C 的 key-value Observing 的功能
- willSet 可以從
newValue
變數取得新值 - didSet 可以從
oldValue
變數取得舊值
class PropObserve {
var aVar: Int = 0 {
willSet {
print("New Value: \(newValue)")
}
didSet {
print("Old Value: \(oldValue)")
}
}
}
Initialization
- Designated initializer: 完全初始化所有 stored property 的 initializer
- Designated initializer: 皆須調用 superclass 的 designated initializer
- Convenience initiailzer: 用
convenience
修飾,必須調用其他 initializer convenience
init chain 的最末一個必須調用 self 的 designated initializer- 利用
required
修飾詞來指定 subclass 必須實作的 initializer - 底層與 Objective-C 同為 Two-Phase Initialization,但會賦予 stored property 實值而非
nil
- subclasses 在滿足下列條件之一,就會自動繼承 superclass 的 initis(預設不繼承):
- subclass 沒有宣告任何 init -> 自動繼承所有 designated init
- subclass 實作所有 superclass 的 designated inits -> 自動繼承所有 convenience init
class MyClass: CustomStringConvertible {
var a: Int
var b: Int
var description: String {
return "\(a), \(b)"
}
init(a: Int, b: Int) {
self.a = a
self.b = b
}
required init(a: Int, b: Int, c: Int) {
self.a = a * 5
self.b = b * c
}
convenience init() {
self.init(a: 0, b: 0)
}
}
class SubClass: MyClass {
override init(a: Int, b: Int) {
super.init(a: a, b: b)
}
required init(a: Int, b: Int, c: Int) {
super.init(a: a, b: b)
self.a = 10
self.b = 15
}
}
let my = MyClass()
print("my: \(my)")
let my2 = MyClass(a: 1, b: 2, c: 3)
print("my2: \(my2)")
let sub = SubClass()
print("sub: \(sub)")
let sub2 = SubClass(a: 1, b: 2, c: 3)
print("sub2: \(sub2)")
let sub3 = SubClass(a: 1, b: 2)
print("sub3: \(sub3)")
Struct
- 和 class 相似,但為 value type,且無法繼承
- Default Initializer 是 memberwise initializer(須賦值給所有 properties)
- mutating method: 改變自身 property
struct Origin {
let x: Int
let y: Int
}
let origin = Origin(x: 0, y: 1) // 提供 default init
struct Point {
let x: Int
let y: Int
init() {
self.x = 0
self.y = 0
}
mutating func multiplyX(x: Int) {
self.x *= x
}
}
let point = Point(x: 0, y: 1)
// Error: custom init 會取代 default memberwise init
- Extra: inherit
OptionSet
protocol forNS_OPTIONS
(不是用 enum)
public struct Direction: OptionSet {
public let rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
static let north = Direction(rawValue: 1 << 0)
static let east = Direction(rawValue: 1 << 1)
static let south = Direction(rawValue: 1 << 2)
static let west = Direction(rawValue: 1 << 3)
static let none: Direction = []
static let all: Direction = [.north, .east, .south, .west]
}
let dir: Direction = [.north, .east]
Enum
- 不需指定 rawValue
- 每個 case 都是被定義好的類型,不需害怕比對到
0
(legacy Objective-C NS_ENUM issue) - mutating method: 改變自身 properties 的值
- 很容易與
switch
配合 - Advance:可以宣告 indirect enum (recursive enum)
Optional
Swift 中很神奇的 Optional,實際上就只是一個 enum,實作上大概長這樣
public enum Optional<Wrapped> {
case none
case some(Wrapped)
}
Unwrapping
- forced unwrapped:強迫解析/取值(不建議使用)
- implicit wrapped:自動解析/取值(Apple 官方用在 xib/storyboard 的 IBOutlet property)
- Nil-coalescing: 提供 Optional 一個 default value
let a: Int? = 10
let b: Int! = 10
print(a!) // print 需傳入非 optional 的型別,必須強迫解析
print(a ?? "This is nil") // a 若是 nil 則 print 出 "This is nil"
print(b) // b 已自動解析(不建議使用,除非完全確定不會取得 nil)
Optional Chaining
- 在 optional 後加上
?
,遇到nil
停止,不會 crash(類似 Objective-C 對 nil msgSend) - 可以 chaining protocol 的 optional method,不需要
respondToSelector:
檢查
let array: [Int]? = [1, 2, 3]
print(array?.count)
// 3
Optional Binding
- 解析 optional 中的 value,有值則將其 binding 到 variable 上,不需再解析
if let
case let
guard let
let x: Int? = 1000
if let x = x {
print(x) // no need to unwrap
} else {
// if x == nil goes here
}
if x != nil {
print(x!) // Need a forced unwrapping. Unsafe.
}
Tuple
- 輕巧的 container,除了儲存 value,沒有其他功能
- tuple elements 可以有 name
- tuple elements 可以是不同型別
- function 可利用 tuple 產生多個 return value
- 可與強大 pattern matching 配合
let voidTuple = ()
let tuple = (1, "A")
print(tuple.0, tuple.1)
// 1 A
let namedTuple = (a: "a", b: "b", "c")
print(namedTuple.a, namedTuple.b, namedTuple.2, namedTuple.0)
// a b c a
let point = (3, 5)
switch point {
case (_, 0):
print("on the x axis")
case (0, _):
print("on the y axis")
case (0, 0):
print("at origin")
case let (x, y): // value-binding
print("at (\(x), \(y))")
}
Function & Method & Closure
- 皆是 reference type,型別為 parameter types + return type,類似 method signature。
- closure 可以利用 capture list 獲取外部變數,防止 retain cycle
- escaping closure:作為參數傳遞時,會延遲至 function return 後執行,逃出 function scope
- 預設 closure 作為 parameter 時為 @noescaing,escaping closure 需以 @escaping 特別修飾
// function witn 0 param, return an Int
func function1() -> Int {
}
// function with 2 params, return a closure with 1 param and return a String
func function2(x: Double, y: String) -> (Int) -> (String) {
return { Int in
return "a string"
}
}
// closure with 2 params, no return value
let closure1 = { (a: Int, b: Int) in
print(a, b)
}
// closure with explicit type declaration (implicit return & shorthand argument)
let closure2: (String, String) -> ([String: String]) = { [$0: $1] }
Protocol
- 與 Objective-C 的 protocol 雷同
- 可以透過
@objc optional
修飾為 optional 的 requirement - 可繼承,也可透過 extension 提供 default implementations
protocol Car {
var tireCount: Int { get set }
var isDriving: Bool { get }
func drive()
}
// Optional 需要 import Foundation 才能利用 Objective-C 的 runtime 特性
import Foundation
@objc protocol Flyable {
func fly()
@objc optional func landing()
}
Extension
- class/struct/enum 皆可宣告 extension
- protocol 的 extension 為該 protocol 的 default implementations
- 同一個 type 可以有多個 extesions
- extension 可以使用
where
來限制 extension 的範圍
import UIKIt
protocol SomeViewProtocol {}
extension SomeViewProtocol where Self: UIButton {
// 作用在 UIButton 的 default implementations
}
extension SomeViewProtocol where Self: UILabel {
// 作用在 UILabel 的 default implementations
}
Access Control
- open/public: 公開
- internal: 模組層(預設的 ACL)
- fileprivate: 文件層
- private: 宣告層(class/struct 內部等)
- patial access control: setter 需要較高權限時(外部 readonly)
- internal(set)
- fileprivate(set)
- private(set)
class MyClass {
open func test() {}
public private(set) var readonlyVar: Int = 0
}
public 與 open 的差異在與 subclassable,open 可以在模組外 subclass,但 public 不能在模組外 subclass
Ecosystem
- SwiftLint: Coding style linter written in Swift
- Carthage: Package Manager written in Swift
- Jazzy: Swift documentation generator (written in Ruby)
- Awesome-Swift: curated list for Swift library/fraemwork
- Server-side Swift
- functional/reactive programming
- RxSwift: ReactiveX Community Support
- ReactiveCocoa: Most Popular RX library