tree_25.jpg
-
11. 閉包表達式(Closure Expression) :
一種函數的定義方式
- 在Swift中,可以通過func定義一個函數,也可以通過閉包表達式定義一個函數
func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
var fn = {
(v1: Int, v2: Int) -> Int in
return v1 + v2
}
fn(10, 20)
{
(v1: Int, v2: Int) -> Int in
return v1 + v2
}(10, 20)
{
(參數列表) -> 返回值類型 in 函數體代碼
}
-
12. 閉包
- 一個函數和它所捕獲的變量\常量環境組合起來,稱為閉包
- 一般指定義在函數內部的函數
- 一般它捕獲的是外層函數的局部變量\常量
- 一個函數和它所捕獲的變量\常量環境組合起來,稱為閉包
typealias Fn = (Int) -> Int
func getFn() -> Fn {
var num = 0
func plus(_ i: Int) -> Int {
num += i
return num
}
return plus
} // 返回的plus和num形成了閉包
輔助理解:
- 可以把閉包想象成是一個類的實例對象
- 內存在堆空間
- 捕獲的局部變量\常量就是對象的成員(存儲屬性)
- 組成閉包的函數就是類內部定義的方法
class Closure {
var num = 0
func plus(_ i: Int) -> Int { num += i
return num
}
}
-
13. 自動閉包
@autoclosure
// 如果第1個數大于0,返回第一個數。否則返回第2個數
func getFirstPositive(_ v1: Int, _ v2: Int) -> Int {
return v1 > 0 ? v1 : v2
}
getFirstPositive(10, 20) // 10
getFirstPositive(-2, 20) // 20
getFirstPositive(0, -4) // -4
// 改成函數類型的參數,可以讓v2延遲加載
func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int? {
return v1 > 0 ? v1 : v2()
}
getFirstPositive(-4) { 20 }
func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int? {
return v1 > 0 ? v1 : v2()
}
getFirstPositive(-4, 20)
- @autoclosure 會自動將 20 封裝成閉包 { 20 }
- @autoclosure 只支持 () -> T 格式的參數
- @autoclosure 并非只支持最后1個參數
- 空合并運算符 ?? 使用了 @autoclosure 技術
- 有@autoclosure、無@autoclosure,構成了函數重載
為了避免與期望沖突,使用了@autoclosure的地方最好明確注釋清楚:這個值會被推遲執行
-
14. 屬性
-
存儲屬性(Stored Property)
- 類似于成員變量這個概念
- 存儲在實例的內存中
- 結構體、類可以定義存儲屬性
- 枚舉不可以定義存儲屬性
- 在創建類 或 結構體的實例時,必須為所有的存儲屬性設置一個合適的初始值
- 可以在初始化器里為存儲屬性設置一個初始值
- 可以分配一個默認的屬性值作為屬性定義的一部分
-
延遲存儲屬性(Lazy Stored Property):
- 使用lazy可以定義一個延遲存儲屬性,在第一次用到屬性的時候才會進行初始化
- lazy屬性必須是var,不能是let(存儲類型屬性除外)
- let必須在實例的初始化方法完成之前就擁有值
- 如果多條線程同時第一次訪問lazy屬性
無法保證屬性只被初始化1次
- 當結構體包含一個延遲存儲屬性時,只有var才能訪問延遲存儲屬性, 因為延遲屬性初始化時需要改變結構體的內存
-
計算屬性(Computed Property)
- 本質就是方法(函數)
- 不占用實例的內存
- 枚舉、結構體、類都可以定義計算屬性
- set傳入的新值默認叫做newValue,也可以自定義
- 只讀計算屬性:只有get,沒有set(有set,必須有get)
- 定義計算屬性只能用var,不能用let。let代表常量:值是一成不變的 ,而計算屬性的值是可能發生變化的(即使是只讀計算屬性)
枚舉原始值rawValue的本質是:只讀計算屬性
-
類型屬性(Type Property):
- 屬性可以分為:
-
實例屬性(Instance Property):只能通過實例去訪問
- 存儲實例屬性(Stored Instance Property):存儲在實例的內存中,每個實例都有1份
- 計算實例屬性(Computed Instance Property)
-
類型屬性(Type Property):只能通過類型去訪問
- 存儲類型屬性(Stored Type Property):整個程序運行過程中,就只有1份內存(類似于全局變量)
- 計算類型屬性(Computed Type Property)
- 可以通過static定義類型屬性
- 如果是類,也可以用關鍵字class
-
ps:類型屬性細節
- 不同于存儲實例屬性,你必須給存儲類型屬性設定初始值。 因為類型沒有像實例那樣的init初始化器來初始化存儲屬性
- 存儲類型屬性默認就是lazy,會在第一次使用的時候才初始化。 就算被多個線程同時訪問,
保證只會初始化一次
。 存儲類型屬性可以是let - 枚舉類型也可以定義類型屬性(存儲類型屬性、計算類型屬性)
-
- 屬性可以分為:
-
// 單例模式
public class FileManager {
public static let shared = FileManager()
private init() { }
}
public class FileManager {
public static let shared = {
// ....
// ....
return FileManager()
}()
private init() { }
}
-
15. 屬性觀察器(Property Observer)
- 可以為非lazy的var存儲屬性設置屬性觀察器
- willSet會傳遞新值,默認叫newValu
- didSet會傳遞舊值,默認叫oldValue
- 在初始化器中設置屬性值不會觸發willSet和didSet
- 在屬性定義時設置初始值也不會觸發willSet和didSet
- 父類的屬性在它自己的初始化器中賦值不會觸發屬性觀察器,但在子類的初始化器中賦值會觸發屬性觀察器
- 屬性觀察器、計算屬性的功能,同樣可以應用在全局變量、局部變量身上
struct Circle {
var radius: Double {
willSet {
print("willSet", newValue)
}
didSet {
print("didSet", oldValue, radius)
}
}
init() {
self.radius = 1.0
print("Circle init!")
}
}
-
16. inout的本質
- 如果實參有物理內存地址,且沒有設置屬性觀察器
- 直接將實參的內存地址傳入函數(實參進行引用傳遞)
- 如果實參是計算屬性 或者 設置了屬性觀察器
- 采取了Copy In Copy Out的做法
- 調用該函數時,先復制實參的值,產生副本【get】
- 將副本的內存地址傳入函數(副本進行引用傳遞),在函數內部可以修改副本的值
- 函數返回后,再將副本的值覆蓋實參的值【set】
- 總結:inout的本質就是引用傳遞(地址傳遞)
- 如果實參有物理內存地址,且沒有設置屬性觀察器
-
17. 方法
-
mutating
結構體和枚舉是值類型,默認情況下,值類型的屬性不能被自身的實例方法修改。
在func關鍵字前加mutating可以允許這種修改行為 -
@discardableResult
在func前面加個@discardableResult,可以消除:函數調用后返回值未被使用的警告
?
-
-
18. 下標(subscript)
- 使用subscript可以給任意類型(枚舉、結構體、類)增加下標功能,有些地方也翻譯為:下標腳本
- subscript的語法類似于實例方法、計算屬性,本質就是方法(函數)
- subscript中定義的返回值類型決定了
get方法的返回值類型
set方法中newValue的類型 - subscript可以接受多個參數,并且類型任意
- subscript可以沒有set方法,但必須要有get方法。如果只有get方法,可以省略。
-
19.繼承(Inheritance)
- 值類型(枚舉、結構體)不支持繼承,只有類支持繼承。
- 沒有父類的類,稱為:基類
- Swift并沒有像OC、Java那樣的規定:任何類最終都要繼承自某個基類
- 子類可以重寫父類的下標、方法、屬性,重寫必須加上override關鍵字
- 重寫類型方法、下標:
1.被class修飾的類型方法、下標,允許被子類重寫
2.被static修飾的類型方法、下標,不允許被子類重寫 - 重寫屬性:
- 子類可以將父類的屬性(存儲、計算)重寫為
計算屬性
- 子類不可以將父類屬性重寫為存儲屬性
- 只能重寫var屬性,不能重寫let屬性
- 重寫時,屬性名、類型要一致
- 子類重寫后的屬性權限 不能小于 父類屬性的權限
- 如果父類屬性是只讀的,那么子類重寫后的屬性可以是只讀的、也可以是可讀寫的
- 如果父類屬性是可讀寫的,那么子類重寫后的屬性也必須是可讀寫的
- 子類可以將父類的屬性(存儲、計算)重寫為
- 重寫類型屬性:
- 被class修飾的計算類型屬性,可以被子類重寫
- 被static修飾的類型屬性(存儲、計算),不可以被子類重寫
- 屬性觀察器:
- 可以在子類中為父類屬性(除了只讀計算屬性、let屬性)增加屬性觀察器
-
final
:- 被final修飾的方法、下標、屬性,禁止被重寫
- 被final修飾的類,禁止被繼承
-
多態(繼承)原理圖示:
多態