Optional chaining ?
以下述例子說明,當建立了 myClass 的 instance 後要如何取得 myClass2 中的 p。
class MyClass {
var c2: MyClass2?
}
class MyClass2 {
var p = 1
}
let c = MyClass()
依照之前學的經驗,需要先對 optionals unwrapping 取出真正的值,才能繼續操作。
c.c2!.p
但上述的程式碼將會造成 error,因為 c2 是 nil,因此需要改成以下的方法,才能安全操作,
以下的方法會回傳 nil。
c.c2?.p
各種 optional chaining
class MyClass {
var c2: MyClass2?
}
class MyClass2 {
var array = [Int]()
var number: Int {
return array.count
}
func test() {
print("123")
}
var c3: MyClass3?
}
class MyClass3 {
var p: String?
}
對 property 讀取或是給予值時,都會是 nil 或是失敗。
let c = MyClass()
c.c2?.number
c.c2?.c3 = MyClass3()
呼叫 method 時,會將所有回傳 type 改為 Optional,因此若沒有回傳值,
會把 Void 改為 Void?,皆會取得 nil。
let c = MyClass()
c.c2?.test()
也可以進行多層的 optionals chaining,邏輯與上述相同。
let c = MyClass()
c.c2?.c3?.p
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
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
子類別宣告
子類別可以讀取到父類別的 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 等等來禁止複寫。
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])
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
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 完全不同,所以使用上也會有不同的意義與方法。
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)
}
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())
宣告
帶有參數與回傳值
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)