over 1 year ago

Apple 在 iOS 5 之後開放 ARC,開發者不需要再自行 release or retain 來操作記憶體,
swift 的 ARC 與 Objc 並沒有差異太大,開發者需要注意的是 code 之間的記憶體管理,
避免造成 memory leak 等等問題。

ARC 只對 reference type 有用,與 structure 或是 enumeration 等 value type 並無相關。

ARC 範例

class MyClass {
    deinit {
    print("deinit")
  }
}
var c: MyClass? = MyClass()
var c1 = c
var c2 = c
c = nil
c1 = nil
c2 = nil

Strong reference cycle

當指定 instance 給予變數或常數時,稱為 strong reference,
每當有一個 strong reference 指向 instance 時,此 instance 的 reference 就會加一,
當 instance 的 reference 歸零時,此 instance 的記憶體就會被系統回收。

在以下的情況,會讓 instance 的 reference 沒有歸零卻也沒有變數或常數 reference,
導致無法被記憶體回收而造成 memory leak。

class MyClass {
    var c: MyClass2?
  deinit {
    print("myClass deinit")
  }
}
class MyClass2 {
    var c: MyClass?
  deinit {
    print("myClass2 deinit")
  }
}
var c: MyClass? = MyClass()
var c2: MyClass2? = MyClass2()
c!.c = c2
c2!.c = c
c = nil
c2 = nil

在上述的兩個 class 內各自擁有對方的變數,初始化出兩個 instance 後,互相指定給對方的 property,
這時候執行 c = nil 時,因為 c2 的 property 還有 strong reference 到 c,因此 c 不會被回收,
執行 c2 = nil 時,因為沒有被回收到 c 的 property 還有 strong reference 到 c2,
因此 c2 也不會被回收,最後的情況變成這兩個 instance 都不會被回收卻也無法再被使用到。

c (strong)-> MyClass instance (strong)-> c2
c2 (strong)-> MyClass2 instance (strong)-> c

MyClass instance (strong)-> c2
c2 (strong)-> MyClass2 instance (strong)-> c

MyClass instance (strong)-> c2
MyClass2 instance (strong)-> c

要解決 strong reference cycle 的方法,可以使用 weak reference 或是 unowned reference,
若使用這兩個 reference 指向 instance 時,此 instance 的 reference 並不會加一。

class MyClass {
    var c: MyClass2?
  deinit {
    print("myClass deinit")
  }
}
class MyClass2 {
    weak var c: MyClass?
  deinit {
    print("MyClass2 deinit")
  }
}
var c: MyClass? = MyClass()
var c2: MyClass2? = MyClass2()
c!.c = c2
c2!.c = c
c = nil
c2 = nil

重新來看,初始化出兩個 instance 後,互相指定給對方的 property,
這時候執行 c = nil 時,因為 c2 的 property 是 weak reference 到 c,
因此 c 此時的 reference 已經歸零並且被回收,
執行 c2 = nil 時,因為 c 已經被回收,因此 c2 的 reference 也會歸零並且被回收。

c (strong)-> MyClass instance (strong)-> c2
c2 (strong)-> MyClass2 instance (weak)-> c

c2 (strong)-> MyClass2

Unowned reference 跟 weak reference 用法相同,但是 unowned reference 必須要有值,
因此不可以使用 optional,所以會用在非常確定不會被回收到的 instance 上。

class MyClass {
    var c: MyClass2?
  deinit {
    print("myClass deinit")
  }
}

class MyClass2 {
    unowned let c: MyClass
  init(para:MyClass) {
    c = para
  }
  deinit {
    print("myClass2 deinit")
  }
}

var c: MyClass? = MyClass()
c!.c = MyClass2(para:c!)
c = nil

在 closures 內也會發生 strong reference cycle,如果 class 內有 closure,
並且在 body 內去呼叫了 self,當 instance 執行時,便會對 instance 產生 strong reference。

class MyClass {
    let s = "123"
    lazy var property: () -> String = {
    return self.s
  }
}
var c: MyClass? = MyClass()
let s = c.property
c = nil

Swift 提供在 closures 內加入 unowned reference 與 weak reference。

class MyClass {
    let s = "123"
    lazy var property: () -> String = { [unowned self = self]
    return self.s
  }
}
var c: MyClass? = MyClass()
let s = c.property
c = nil
 
over 1 year ago

Initialization

Classes 與 structures 在建立 instance 時,所有的 property 一定都需要有初始值,
若在宣告 property 時未給予預設值,就必須在 initializer 內來設定。

Initializer 宣告

基本宣告

class MyClass {
    var property: Int
    var property2 = 123
    init () {
        property = 123
    }
}

客製化宣告

class MyClass {
    var property: Int
    init (para: Int) {
        property = para
    }
}

預設 initializer
若 property 全都有預設值,就會自動產生一個不需帶任何參數的 initializer。
但如果有宣告其他的 initializer,這個 initializer 就不會自動產生。

Structures 的 initializer

之前有介紹到,如果 structures 有 property,會自動產生帶有參數的 initializer。

struct MyStruct {
    var property: Int
}
let s = MyStruct(property: 123)

Designated initializers & convenience initializers

Designated initializers 是 class 內最主要的初始化方法,
class 內至少要有一個 designated initializer。
Convenience initializers 是 class 內次要的初始化方法,
class 內可以沒有 convenience initializers。
宣告方法
Designated initializers

class MyClass {
    init() {
    }
}

Convenience initializers

class MyClass {
    convenience init () {
    }
}

重點規則
每個 subclass 內的 designated initializers 一定要呼叫 superclass 的 designated initializers。

class MyClass {
    init() {
    }
    init(para: String) {
    }
}
class MyClass2: MyClass {
    init(para: Int) {
        super.init(para: 123")
    }
}

每個 convenience initializers 一定要呼叫同一個 class 內其他的 initializers。

class MyClass {
    init() {
    }
    convenience init(para: Int) {
        self.init()
    }
}

Convenience initializers 最後一定要呼叫到 designated initializers。

class MyClass {
}
class MyClass2: MyClass {
    init(para: String) {
    }
    convenience init(para: Int) {
        self.init(para: 123")
    }
    convenience init(para: Int, para2: Int) {
        self.init(para: para)
    }
}

繼承 initializers

Swift 的 subclass 並不會繼承 superclass 的 initializers。
但如果 subclass 沒有宣告任何 designated initializers,
subclass 會自動繼承 superclass 所有的 initializers。

class MyClass {
    init(para: Int) {
    }
}
class MyClass2: MyClass {
}
let c = MyClass2(para:123)

如果 subclass 重新宣告 superclass 全部的 designated initializers,
那麼 subclass 就會自動繼承 superclass 所有的 convenience initializers。

class MyClass {
    init() {
    }
    init(para: Int) {
    }
    convenience init(para: String) {
    }
}
class MyClass2: MyClass {
    override init(para: Int) {
        super.init()
    }
}
let c = MyClass2(para:123")

Fallible initializers

可以讓初始化後回傳 Optionals 的 nil,代表初始化失敗。

class MyClass {
    init?(para: String) {
        if para.isEmpty { return nil }
    }
}

也可以讓 subclass override superclass 的 fallible initializers,
變成 non fallible initializers。

class MyClass {
    init() {
    }
    init?(para: String) {
        if para.isEmpty {return nil}
    }
}
class MyClass2: MyClass {
    override init(para: String) {
        super.init()
    }
}

必要 initializer

如果 subclass 有定義自己的 designated initializers,
那 subclass 就必須一定要實作 superclass 上有加 required 的 initializer。

class MyClass {
    required init(para: String) {
    }
}
class MyClass2: MyClass {
    init() {
    }
    required init(para: String) {
    }
}

透過 closure 設定 property 初始值

可以直接透過 closure 來設定 property,在 instance 建立時,會同時呼叫這個 closure 來設值,
要記住切勿在此 closure 內使用其他的 property,因為這時候的 instance 尚未被建立,
其他的 property 可能還沒被初始化。

class MyClass {
    let para = { ()->String in
        return 123
    }()
}

Deinitialization

Deinitializer 會在 instance 被 dealloc 時被自動呼叫,也沒有任何方法能夠主動去呼叫。

class MyClass {
    deinit {
    print("deinit")
  }
}
let c = MyClass()
c = nil
 
over 1 year ago

子類別宣告

子類別可以讀取到父類別的 property 或是 method,也可以自己新增。

class MyFirstClass {
    var first = 123
}
class MySecondClass: MyFirstClass {
    var second = 456
}
let c = MySecondClass()
c.first
c.second

子類別本身也可以當父類別

class MyFirstClass {
    var first = 123
}
class MySecondClass: MyFirstClass {
    var second = 456
}
class MyThirdClass: MySecondClass {
    var third = 789
}
let c = MyThirdClass()
c.first
c.second
c.third

複寫

Override 是複寫的關鍵字,包含 method, property 與 subscript 都可以進行覆寫。
Super 關鍵字是用在實作複寫內容時,可以先去呼叫原始被覆寫的內容。

複寫 methods

class MyFirstClass {
    func myFunc() {
    }
}
class MySecondClass: MyFirstClass {
    override func myFunc() {
        super.myFunc()
    }
}

複寫 property

  • 可以將 read-only 的 property 複寫成 read-write。
  • 不可以將 read-write 的 property 複寫成 read-only。
  • 若複寫 setter,就必須複寫 getter。
class MyFirstClass {
    var p: Int {
        get {
            return 123
        }
        set {
        }
    }
}
class MySecondClass: MyFirstClass {
    override var p: Int {
        get {
            return super.p
        }
        set {
        }
    }
}

複寫 property observer
要注意不可以對常數與 read-only 的 property 複寫 property observer,
也不能夠對同一個 property 同時複寫 getter setter 與 observer,
複寫 property observer 時,父類別原本的 observer 一定會被呼叫到。

class MyFirstClass {
    var p: Int = 1 {
        didSet {
        }
    }
}
class MySecondClass: MyFirstClass {
    override var p: Int {
        didSet {
        }
    }
}
let c = MySecondClass()
c.p = 123

禁止複寫
final 關鍵字提供給 method, property, var, subscript 與 class 等等來禁止複寫。

 
over 1 year ago

Methods

Methods 其實就是跟特定型別有相關的 functions。

實體 methods

宣告方法與 functions 相同。

class MyClass {
    func myFunc(para: Int) -> Int {
        return para
    }
}
let c = MyClass()
c.myFunc(para:123)

self

每個 instance 都有 self 這個 property,代表這個實體本身。

class MyClass {
    var para = 1
    func myFunc() -> Int {
        return self.para
    }
}

在上述的情況下,不需要特別寫 self,因為 Swift 會假設是 instance 的變數。
但在下述情況,就需要使用 self 來區分。

class MyClass {
    var para = 1
    func myFunc(para: Int) -> Int {
        return self.para + para
    }
}

在實體變數內修改 property

class MyClass {
    var para = 1
    func myFunc() {
        para = 2
    }
}

上述在 class 內可以執行,但因為 struct 跟 enumeration 是 value type,
所以不能夠直接在 method 內修改 property,若要這樣做,必須加上 mutating。

struct MyStruct {
    var para = 1
    mutating func myFunc() {
        para = 2
    }
}

在 method 內指定 self

在 mutating method 內,可以建立新的 instance 並給予 self 來取代原本的 instance。

struct MyStruct {
    mutating func myFunc() {
        self = MyStruct()
    }
}

Methods 類型

Classes 可使用 class,structures 或是 enumerations 可使用 static,透過型別來呼叫而非實體。

class MyClass {
    class func myFunc() {
    }
}
MyClass.myFunc()
struct MyStruct {
    static func myFunc() {
    }
}
MyStruct.myFunc()

Subscripts

在 classes, structures 與 enumerations 中可以定義 subscripts,
目的是透過簡潔的方法去對 collection element 讀取或賦予值。

class MyClass {
    var array = [1,2,3,4]
    subscript(index: Int) -> Int {
        get {
            if index >= array.count {
                return 0
            }
            return array[index]
        }
        set {
            if index >= array.count {
                array.append(newValue)
                return
            }
            array.insert(newValue, at:index)
        }
    }
}
let c = MyClass()
c[3] = 123
print(c[0])
 
over 1 year ago

Property 基本宣告

前一章有提到在 class 與 structure 內設定 property。

class MyClass {
    var p = 0
}

Structures 可以在初始化時同時初始 property。

struct MyStruct {
    var p: Int
}
let s = MyStruct(p:123)

Lazy property

宣告 Lazy 關鍵字的 property 會直到被呼叫到才會初始化,一定要給予初始值。

class MyClass {
    lazy var p = 0
}

運算 property

實際上的功用不是儲存值,而是用來做其他的運算。

class MyClass {
    var value1 = 1
    var value2 = 2
    var computed: Int {
        get {
                return value1 + value2
        }
        set (new){
                value2 = newValue
        }
    }
}

set 中的 new 參數可以省略,直接用預設的 newValue 來操作也可以。
read-only 可以透過不寫 set 來實作,且可以直接省略 get 關鍵字。

class MyClass {
    var readonly: String {
        return "123"
    }
}

Property 觀察

對 property 設定 willSet 與 didSet,在給予值的時候會被呼叫。

class MyClass {
    var p = 0 {
        willSet {
        }
        didSet {
        }
    }
}

Static property

Static 關鍵字可以讓 property 不被侷限於一個實體內,而是所有相同類別的實體共同使用。

class MyClass {
    static var p = 1
}
MyClass.p
 
over 1 year ago

Classes 宣告語法

class MyClass {
}

Class 實體

class MyClass {
}
let c = MyClass()

Class 取得 property

class MyClass {
     let p = 0
}
let c = MyClass()
c.p

Class 是 reference type

Structure 宣告語法

struct MyStruct {
}

Structure 實體

struct MyStruct {
}
let s = MyStruct()

Structure 取得 property

struct MyStruct {
    var p = 0
}
let s = MyStruct()
s.p

Structure 初始化 property

struct MyStruct {
    var p: Int
}
let s = MyStruct(p: 123)

Structure 是 value type

Classes 與 Structures 相通的功能

  • 定義 property 與儲存變數
  • 定義方法來提供使用功能
  • 定義 subscript 來取得變數
  • 定義初始化方法
  • 可以使用 extension 來擴增原本沒有定義的功能
  • 可以透過 protocol 提供標準功能

Classes 特有的功能

  • 繼承
  • 確認實體隸屬於哪個 class
  • 定義毀滅化方法
  • Classes 屬於 reference types,因此一個 class 實體可以有複數個 reference

Classes 與 Structures 的選擇

在 Swift 中,classes 與 structure 的功能相當接近,兩者都能建立出 instance,
因此在建立 instance 前,更要先去設計並且思考實際上的使用情境,並選出符合情境的。

例如 Swift 的 string, array 等基本資料型態都是屬於 structure,
因此在 pass 這些 instance 時都會 copied 一份,
這個與 Objc 完全不同,所以使用上也會有不同的意義與方法。

 
over 1 year ago

Enumerations 定義了一組值,每個值在型別上都屬於這個 enumeration,而每個值可以有任何型別的初始值。

宣告語法

enum myEnum {
    case a
    case b
    case c
}
var e = myEnum.a

因為 e 的型別宣告為 myEnum,所以可以直接指定 myEnum 的 value。

e = .b

Enumerations 值的型別

對 enum 內部的值定義相關的型別,且只能儲存單一的值,也就是如果先給予 a 值,再給予 b,只會保留 b。
取出的方法是使用 switch 取出。

enum myEnum {
    case a(Int, Int)
    case b(String)
}
var e = myEnum.a(1,2)
e = .b("12")

swith e {
case .a(let v1, let v2):
    print("a")
case .b(let value):
    print("b")
}

Enumerations 值的初始值

設定 enum 的初始值,代表內部值的初始值都是相同型別。

enum myEnum: String {
    case a = "a"
    case b = "b"
}

如果使用 Int,定義第一個 case,後續會按照順序自動加上。

enum myEnum: Int {
    case a = 1, b, c ,d
}

如果使用 String 型別,會自動以 enum 的值名稱當做值。

enum myEnum: String {
    case a
    case b
}

取得初始值的方法。

enum myEnum: String {
    case a
    case b
}
let e = myEnum.a.rawValue

透過 rawValue 宣告 enum 型別

如果初始值沒有在 enum 內的值定義,會回傳 nil,一般會回傳 optionals。

enum myEnum: String {
    case a
    case b
}
let e = myEnum(rawValue: "123")

遞迴 enum

若要把 enum 當作 enum 值的型別,需要在 enum 前面加上 indirect。

indirect enum myEnum {
    case a
    case b
    case c(a,b)
}
 
over 1 year ago

Closures 代表具有功能性且可以被當作參數傳遞的程式區段,
之前提到的 function 就是 closures 的一種類型,屬於有命名且無法儲存值的 closures。
這篇主要講解的 closures 類型是是沒有命名且可以儲存值。

宣告與簡化

基本宣告方法。

{ (para: String) -> String in
    return para
}

如果 body 只有一行的話。

{ (para: String) -> String in return para }

如果 closure 當作參數傳遞,因為 swift 會推斷參數的型別,所以可以省略參數與回傳值的型別定義。

{ para -> in return para }

如果 closure 只有一行的話,return 可以省略。

{ para -> in para }

如果 body 使用到參數來執行且只有一行,可以使用系統定義的 $0,$1 等參數名稱並且省略 in。

{$0}

Closure 傳遞
將 closure 當作 function 的參數傳遞。
沒有參數與回傳值。

func myFunc (closure: () -> Void) {
}
self.myFunc{
}

有參數。

func myFunc (closure: (_ para: String) -> Void) {
}
self.myFunc { (para) in 
}

有回傳值。

func myFunc (closure: () -> String) {
}
self.myFunc { () -> String in 
}

以上的 sample 都是呼叫時將 closure 放置到最後面,也可以照原本帶入參數的方法將 closure 放在括號內,
以下 sample 是兩種不同的方式。

func myFunc(para: Int closure: (_ para:String)->Void) {
}
self.myFunc(para:123 closure: { (para) in 
})
self.myFunc(para:123) { (para) in 
}

擷取值

之前有提到在 function 內定義 function,這個部分跟在 function 內定義 closure 相同,
在 function 內宣告的變數或常數,可以被 function 內的 closure 來使用。

func myFunc(para: Int) -> () -> Int {
    var result = 0
    return { () -> Int in 
            result += para
            return result
           }
}
self.myFunc(para: 123)()

Closures 是 reference types

跳脫 closures

在 function 內使用 closure 型別參數時,都必須要使用呼叫,
若是需要等到整個 function 結束後才進行呼叫,
例如在另外一個 closure 內執行或是放到 array 內之後執行,
參數前面需要加上 @escaping。

func myFunc(closure: @escaping () -> Int) -> () -> Int {
    return closure
}

自動轉 closures

如果在 function 內宣告一個 closure 型別參數的時候,並且在參數前面加上 @autoclosure,
這時參數可以直接帶進來 closure 回傳值,會自動轉成 closure 型別。

func myFunc(closure: @autoclosure () -> int) {   
}
func closureTest() -> Int {
    return 123
}
myFunc(closure: closureTest())
 
over 1 year ago

宣告

帶有參數與回傳值

func myFunc(para: String) -> String {
    return "ok"
}

無參數

func myFunc() -> String {
    return "ok"
}

多個參數

func myFunc(para: String, para2: Int) -> String {
    return "ok"
}

無回傳值

func myFunc(para: String) {
}

利用 tuple,多個回傳值

func myFunc() -> (value: Int, value2: Int) {
    return (2,3)
}

參數命名

以下的宣告方法,在外部呼叫 function 與 function 內部使用的參數名稱都相同。

func myFunc(para: String) {
    print(para)
}
myFunc(para:"abc")

在外部呼叫時使用別的參數名稱。

func myFunc(myPara para: String) {
    print(para)
}
myFunc(myPara:"abc")

在外部呼叫時不使用參數名稱。

func myFunc(_ para: String) {
    print(para)
}
myFunc("abc")

預設參數

若在宣告 function 時,設定參數預設值,呼叫時可以不需要帶入參數。

func myFunc(para: Int = 12) {
}
myFunc()

多值參數

宣告 function 的參數時,可以在單一參數內帶入多個相同型別的值,
內部會使用 array 的方法來處理。

func myFunc(_ para: Int) {
    for num in para {
        num
    }
}
myFunc(1,2,3,4,5)

改動參數

Function 的參數預設是無法被改變,如果有需要更動的話,可以在宣告時定義 inout,
帶入的參數必須是變數,使用 & 符號帶入。

func myFunc(para: inout Int) {
    para = 12
}
var num = 0
myFunc(para:&num)

型別

將變數或常數的型別使用 function 型別宣告。

func myFunc(para: Int) -> Int {
    return 1
}
let vFunc: (Int)->Int = myFunc
vFunc(123)

將 function 型別當作參數型別。

func testFunc(para: Int) -> Int {
    return para
}
func myFunc(para: (Int)->Int, _ para2: Int) -> Int {
    return para(para2)
}
myFunc(para:testFunc, 123)

將 function 型別當作回傳值型別。

func testFunc(para: Int) -> Int {
    return para
}
func myFunc() -> (Int) -> Int {
    return testFunc
}
let result = myFunc()
result(123)

Function 內定義 function

func myFunc() -> (Int) -> Int {
    func testFunc(para: Int) -> Int {
        return para
    }
    return testFunc
}
let result = myFunc()
result(123)
 
over 1 year ago

For-In Loops

前面像是 array 或是 dictionary 如果取 index 或是 key value 就不再敘述。
若不需要用值只想要跑迴圈的話,可以用底線來省略值。

for _ in 15 {
}

While

跟一般 while 一樣,條件是 true 就會進入 body,一直到條件為 false 才會跳出。

var k = 0
while k > 10 {
    k += 1
}

也有類似其他語言的 do while 用法,此用法第一次跑迴圈時不會經過條件判斷。

let k = false
repeat {
    print("loop")
} while k

Switch

  • Swift 的 switch 判斷不局限於整數,可以用許多不同的型別。
  • Swift 的 switch 若遇到符合的 case,會直接結束,不會再跑下一個 case, 所以不需要用 break 跳開,但如果需要的話還是可以在 switch 內使用 break。
  • Swift 的 case 內不可為空的,否則會有 error。
    let s = "a"
    switch s {
    case "a":
        print ("a")
    case "A":
        print ("b")
    default:
        break
    }
    
    可以在 switch 的判斷內做數量判斷。
    let count = 100
    switch count {
    case 0:
        break
    case 1..<=50:
        break
    case 51..<=100:
        break
    default:
        break
    }
    
    可以透過 switch 對 tuple 做判斷,用底線可以代表忽略 tuple 內的這個位置。
    let t = (1,2,3,4)
    switch t {
    case (0,0,0,0):
        break
    case (,2,,_):
        break
    default:
        break
    }
    
    如果有需要的話,可以將判斷的值宣告成新的變數或常數。
    let k = 123
    switch k {
    case 1..<100:
        break
    case let x
        x
    }
    
    Switch 的判斷內可以加上 where 做額外的判斷
    let k = 123
    switch k {
    case 1..<100:
        break
    case let x where x > 120:
        x
    default:
        break
    }
    
    可以把多種 case 條件混合在一個 case 內。
    let k = 123
    switch k {
    case 1,2,3,4,5,6:
        break
    default:
        break
    }
    

Fallthrough

Fallthrough 只能用在 switch 內,用途是可以跳到最後一個 case。

let k = 123
switch k {
case 1,2,3,4,5,6:
        break
case 123:
        fallthrough
default:
        print(123")
    break
}

命名

幫 while, for-in 等等的 loop 方法來命名,可以在另外的判斷或是 loop 內來操作特定的 loop。
以下的例子是雙重迴圈,對外層迴圈來命名,並且在內層迴圈來停止最外層迴圈,讓整個迴圈停止。

testLoop: for _ in 15 {
    for index in 15 {
        if index == 1 {
            break testLoop
        }
    }
}

命名方法就是參照上述的例子,後方可用 while 或 switch 來取代,也可以用 continue 等關鍵字來操作 loop。

Guard

  • Guard 一定是判斷條件為 true。
  • Guard 一定要有 else,且 else 內一定要有 return 或是 break 等等跳開的方法。
    let num = 5
    guard num == 5 else {
        return
    }
    
    可以用 guard 判斷 Optionals 是否為 nil, 若 optionals 有值且後續想要直接使用的話,可以像之前提到與 if 一樣的方法來打開, 可以快速的同時判斷 optionals 是否為 nil 並取值。
    let num: Sting? = "123"
    guard let value = num else {
        return
    }
    value