本文主要講述了屬性凉逛、初始化器、方法、下標而账。雖然是以對象為例進行講解称龙,但大部分也可以使用在結(jié)構(gòu)體留拾、枚舉中。當然也要清楚他們?nèi)咧暗膮^(qū)別鲫尊。對于屬性痴柔,Swift更具體的劃分了存儲屬性和計算屬性,并且可以更方便的為存儲屬性增加屬性觀察器疫向。對于初始化器咳蔚,需要著重理解Swift為實例的安全使用所進行的規(guī)范化扛施,設(shè)定了兩段式初始化和安全檢查來確保初始化過程的安全,只有初始化完成后才可以進行實例的使用屹篓。方法的定義和實現(xiàn)沒有特殊性疙渣。只不過Swift顯式的增加了下標函數(shù),可以更方便的操作存儲屬性
主要內(nèi)容:
- 屬性
- 初始化器
- 方法
- 下標
1堆巧、屬性
Swift中的屬性包括實例屬性和類型屬性妄荔,又分為存儲屬性和計算屬性。存儲屬性可以類比OC的成員變量谍肤,計算屬性可以類比OC的屬性啦租,但是它并沒有成員變量
1.1 存儲屬性
存儲屬性相當于是OC的實例變量,沒有setter和getter荒揣,它直接存儲在實例的內(nèi)存中篷角,結(jié)構(gòu)體、類可以定義實例存儲屬性,枚舉不可以定義痒给。
代碼:
/*
1滑沧、存儲屬性和計算屬性
*/
func test1() {
struct Circle {
//存儲屬性
var radius: Double
//計算屬性
var diameter: Double {
set {
radius = newValue / 2
}
get {
radius * 2
}
}
}
let circle = Circle(radius: 5)
print(circle.radius)
print(circle.diameter)
}
test1()
1.1.1 延遲存儲屬性
延遲存儲屬性lazy Stored Property,使用lazy修飾嘉蕾,在第一次使用屬性的時候才會進行初始化
代碼:
/*
2、延遲存儲屬性
*/
func test2() {
class PhotoView {
//這是一個存儲屬性霜旧,直接將閉包表達式結(jié)果賦值Image
lazy var image: Image = {
let url = "https://image.baidu.com/xx.png"
let data = Data(url: url)
return Image(data: data)
}()
}
}
說明:
- 如果想要在使用屬性的時候才去加載屬性错忱,那么就應該設(shè)置成lazy
- 比如這里的image加載,需要涉及網(wǎng)絡加載挂据,就應該使用lazy
- lazy因為是要在使用時才會執(zhí)行以清,所以必須是var修飾,不能用let
- 如果多條線程同時第一次訪問lazy屬性崎逃,無法保證屬性只被初始化一次
1.1.2 屬性觀察器
監(jiān)聽屬性的修改
代碼:
/*
3掷倔、屬性觀察器
*/
func test3() {
struct Circle {
var radius: Double {
willSet {
print("willSet", newValue)
}
didSet {
print("didSet", oldValue, radius)
}
}
init() {
self.radius = 1.0//不會觸發(fā)觀察器
print("Circle init!")
}
}
// Circle init!
//willSet 2.0
//didSet 1.0 2.0
var cicle = Circle()
cicle.radius = 2.0
}
test3()
說明:
- 觀察器有兩個方法,一個是willSet婚脱,一個是didSet
- 分別是在屬性將要修改今魔、屬性修改完成的時候執(zhí)行
- willSet會傳遞新值,默認叫newValue
- didSet會傳遞舊值障贸,默認叫做oldValue
- 在初始化器中和屬性定義時設(shè)置不會觸發(fā)觀察(這個也容易理解错森,這個沒有觀察的意義)
- willSet和didSet方法其實是包含在setter方法中的
1.2 計算屬性
計算屬性就相當于OC的屬性,有g(shù)etter方法就決定了是計算屬性篮洁,它作為函數(shù)涩维,不占用實例的內(nèi)存,枚舉、結(jié)構(gòu)體瓦阐、類都可以定義計算屬性
代碼:
//計算屬性和存儲屬性的區(qū)別
struct Circle {
//存儲屬性
var radius: Double
//計算屬性
var diameter: Double {
set {
radius = newValue / 2
}
get {
radius * 2
}
}
}
let circle = Circle(radius: 5)
print(circle.radius)
print(circle.diameter)
//只讀計算屬性
struct Circle2 {
//存儲屬性
var radius: Double
//計算屬性
var diameter: Double {
get {
radius * 2
}
}
}
//簡寫
struct Circle3 {
//存儲屬性
var radius: Double
//計算屬性
var diameter: Double { radius * 2 }
}
說明:
- set傳入的新值默認叫做newValue蜗侈,也可以自定義
- 計算屬性只能是var,不能用let修飾睡蟋,這也容易理解踏幻,既然定義了計算屬性說明肯定是要變化的
- 計算屬性的本質(zhì)就是函數(shù)
- 如果只有一個get方法,那么是只讀計算屬性戳杀,此時可以簡寫
枚舉原始值rawValue的本質(zhì)
代碼:
//枚舉的rawValue的本質(zhì)就是只讀計算屬性
enum TestEnum: Int {
case test1 = 1, test2 = 2, test3 = 3
var rawValue: Int {
switch self {
case .test1:
return 10
case .test2:
return 11
case .test3:
return 12
//條件全部保證该面,就不需要增加default
// default:
// <#code#>
// }
}
}
}
print(TestEnum.test3.rawValue)//12
說明:
- 枚舉原始值rawValue的本質(zhì)就是:只讀計算屬性
- 此時枚舉只存儲序號,并沒有存儲1.2.3信卡,他們的獲取就是通過rawValue來計算的
驗證:
查看執(zhí)行TestEnum.test3.rawValue語句的匯編打印
1.3 inout對于屬性的傳遞
屬性作為inout修飾的參數(shù)傳遞隔缀,他們的傳遞方式和普通的變量有一定的不同
代碼:
/*
5、屬性使用inout的分析
*/
func test5() {
//定義一個結(jié)構(gòu)體
struct Shape {
var width : Int
var side : Int {
willSet {
print("willSetSide", newValue)
}
didSet {
print("didSetSide", oldValue, side)
}
}
var girth: Int {
set {
width = newValue / side
print("setGirth", newValue)
}
get {
print("getGirth")
return width * side
}
}
func show() {
print("width=\(width), side=\(side), girth=\(girth)")
}
}
//定義一個函數(shù)傍菇,傳入屬性
func test(_ num: inout Int) {
num = 20
}
//傳入函數(shù)
var s = Shape(width: 10, side: 4)
test(&s.width)//傳入存儲屬性
s.show()
test(&s.side)//傳入帶觀察器的存儲屬性
s.show()
test(&s.girth)//傳入計算屬性
s.show()
}
test5()
說明:
- 首先可以看到這三種都可以作為inout修飾的參數(shù)傳遞猾瘸,傳遞進地址值,之后修改
- 對于存儲屬性直接將屬性的地址傳入丢习,在函數(shù)體內(nèi)修改后外部的存儲屬性自然就改變了
- 計算屬性使用inout牵触,也是傳遞的地址,它會先調(diào)用getter方法將其放到一個椃毫欤空間中荒吏,將這個地址傳遞到函數(shù)中,在函數(shù)體內(nèi)執(zhí)行完成后渊鞋,再調(diào)用setter方法
- 屬性觀察器的存儲屬性,在賦值時其實調(diào)用的是setter方法瞧挤,willSet和didSet方法其實是包含在setter方法中的锡宋。它在作為輸入輸出參數(shù)的時候,也是會先將值賦到局部變量中特恬,之后將局部變量的地址作為參數(shù)傳遞到函數(shù)中進行修改执俩。修改后將這個局部變量通過調(diào)用set方法賦值到存儲屬性中
理解:
- inout修飾的參數(shù),傳入地址后癌刽,在函數(shù)體內(nèi)會直接將數(shù)值賦值到這個地址中役首。
- 所以對于一般的存儲屬性可以直接將地址傳進去。而對于屬性觀察器以及計算屬性显拜,對其設(shè)值需要調(diào)用一下set方法或者get方法衡奥,此時就不能直接把地址賦值進去,因為inout修飾的參數(shù)是直接將值賦值到地址空間上远荠,不會調(diào)用set和get方法
- 需要在外界重新賦值一次矮固。所以需要在外界先創(chuàng)建一個局部變量,將這個局部變量傳遞到函數(shù)中去執(zhí)行譬淳,執(zhí)行后档址,將修改的局部變量賦值到屬性中就會觸發(fā)set方法和get方法盹兢。
小結(jié):
- inout的本質(zhì)就是引用傳遞
- 存儲屬性直接將地址值傳入
- 帶有屬性觀察器的屬性或者計算屬性會先將屬性值拷貝到一個空間,再將這個空間地址傳到函數(shù)中進行修改守伸,修改后再將這個空間的值賦值給屬性
1.4 類型屬性
類型屬性只能通過類型訪問绎秒,整個程序只有一份內(nèi)存,可以看做是其他語言的類屬性或者靜態(tài)屬性
代碼:
//類型屬性
struct Car {
static var count: Int = 0
init() {
Car.count += 1
}
}
let c1 = Car()
let c2 = Car()
print(Car.count)//3
說明:
- 通過static修飾的靜態(tài)屬性就是類型屬性
- 在class中可以通過class修飾的屬性也是類型屬性尼摹,class只能修飾class的類型屬性
注意:
- 類型屬性必須設(shè)定初始值见芹,因為類型不會創(chuàng)建實例,也就不會調(diào)用Init初始化器窘问,所以也就不會在初始化器中設(shè)置辆童,因此需要在屬性中直接設(shè)置默認值
- 存儲類型屬性默認就是lazy,第一次使用的時候才會初始化
- 就算被多個線程同時訪問惠赫,也是線程安全的
- 存儲類型屬性可以是let
單例模式案例:
代碼:
//類型屬性實現(xiàn)單例模式
class FileManager {
//屬性獲取
static let shared = {
FileManager()
}()
//私有無法調(diào)用
private init() { }
}
//兩個對象完全一樣
/*
對象.(unknown context at $1000039a8).(unknown context at $100003a38).FileManager
對象.(unknown context at $1000039a8).(unknown context at $100003a38).FileManager
*/
print(FileManager.shared)
print(FileManager.shared)
說明:
- static修飾說明是類型屬性把鉴,因此保證只要一份
- static修飾說明是線程安全的
- let可以實現(xiàn)只賦值一次
- 這里不需要使用lazy修飾,就是默認懶加載
- 通過打印也可以看到兩個對象是同一個
1.5 注意
- 在創(chuàng)建類或結(jié)構(gòu)體的實例時儿咱,必須為所有的實例存儲屬性設(shè)置一個合適的初始值
- 因為在編譯時會創(chuàng)建這個結(jié)構(gòu)體或類的內(nèi)存
- 可以在初始化器里設(shè)置庭砍,也可以直接在存儲屬性上分配一個默認屬性值
- 如果多條線程同時第一次訪問lazy屬性,是線程不安全的
- 當結(jié)構(gòu)體中包含一個延遲存儲屬性時混埠,這個結(jié)構(gòu)體變量只能用var修飾的時候才可以訪問延遲屬性怠缸,對于延遲存儲屬性,當使用這個屬性時钳宪,就會給它賦值揭北,而給它賦值時就會改變結(jié)構(gòu)體的結(jié)構(gòu),所以結(jié)構(gòu)體變量是不可以用let的
- 屬性觀察器吏颖、計算屬性的功能也可以應用在全局變量搔体、局部變量身上
- Swift的計算屬性不會自動生成存儲屬性
- 如果想要直接存下來這個值,就用存儲屬性半醉,如果是需要通過計算得到的值疚俱,那就用計算屬性。其實就和OC的實例變量和屬性一樣
- 實例屬性通過實例訪問缩多,類型屬性通過類型訪問
- 枚舉類型不可以定義存儲實例屬性呆奕,但可以定義存儲類型屬性,計算屬性
2衬吆、初始化器
初始化器在Swift中強制區(qū)分為指定初始化器和便捷初始化器梁钾,類、結(jié)構(gòu)體咆槽、枚舉都可以定義初始化器陈轿,初始化器的調(diào)用規(guī)則用以確保所有的初始化器都能夠初始化所有的實例,還通過兩段式初始化和安全檢查確保初始化的安全。
2.1 初始化器的分類
指定初始化器
定義
//指定初始化器
init(parameters) {
statements
}
說明:
- 每個類至少有一個指定初始化器麦射,指定初始化器是類的主要初始化器蛾娶,也可以有多個指定初始化器,但是一個類通常只有一個指定初始化器
- 類的指定初始化器用來作為默認初始化器
- 指定初始化器要初始化當前類的所有實例
便捷初始化器
定義
//便捷初始化器
convenience init(parameters) {
statements
}
說明:
- 便捷初始化器需要使用convenience來修飾
- 便捷初始化器需要調(diào)用便捷初始化器或指定初始化器
2.2 調(diào)用規(guī)則
示意圖:
說明:
- 指定初始化器必須從它的直系父類調(diào)用指定初始化器(指定初始化器不能調(diào)用同類的指定初始化器潜秋,只能調(diào)父類的)
- 便捷初始化器必須從同一類中調(diào)用另一個初始化器(可以是便捷初始化器蛔琅,也可以是指定初始化器,必須是同一類中峻呛,便捷初始化器不能調(diào)用父類的初始化器)
- 便捷初始化器最終必須調(diào)用一個指定初始化器,也就是說調(diào)用鏈的最后必須是一個指定初始化器
2.3 兩段式初始化和安全檢查
第一階段初始化所有存儲屬性罗售,第二階段使用實例,通過兩段式必須先給實例初始化钩述,再使用實例可以讓實例更加安全寨躁。安全檢查就是檢查是否遵守初始化器的規(guī)則
2.3.1 兩段式初始化
第一階段:
- 外層調(diào)用指定/便捷初始化器
- 分配內(nèi)存給實例,但未初始化
- 指定初始化器先將當前類定義的存儲屬性都初始化(如果調(diào)用的是便捷初始化器牙勘,最后也會調(diào)用到本類的指定初始化器)
- 指定初始化器調(diào)用父類的初始化器职恳,不斷向上調(diào)用,形成初始化器鏈
第二階段:
1方面、從頂部初始化器開始向下放钦,鏈中的每一個指定初始化器都有機會進一步定制實例
2、初始化器現(xiàn)在能夠使用self來定制實例(訪問恭金、修改它的屬性操禀,調(diào)用它的實例方法等等)
3、最終横腿,鏈中任何便捷初始化器都有機會定制實例以及使用self
示例代碼:
/*
2颓屑、兩段式初始化
*/
func test7() {
class Person {
var age: Int
init(age: Int) {
self.age = age
self.age = 10
}
}
class Student : Person {
var score: Int
init(age: Int, score: Int) {
self.score = score
super.init(age: age)
self.score = 100
}
convenience init() {
self.init(age: 0, score: 0)
}
}
var student: Student = Student()
}
test7()
說明:
- 子類調(diào)用便捷初始化器來初始化,便捷初始化器會調(diào)用該類的指定初始化器
- 子類的初始化器先初始化本類的存儲屬性,再調(diào)用父類的初始化器耿焊,由下到上
- 子類和父類的初始化完成后邢锯,才可以進行個性化定制。
- 先進行父類的個性化定制搀别,再進行子類的,右下到上
分析:
- 個性化定制是由上到下尾抑,而且要在初始化后執(zhí)行的
- 先將自己的存儲屬性都初始化歇父,之后再調(diào)用父類的初始化器,這樣在初始化結(jié)束后就可以進行設(shè)值使用了
- 必須先初始自己的存儲屬性再愈,再調(diào)用父類初始化器榜苫,就是為了在繼承體系中的每個初始化器的最后都可以使用self,設(shè)值翎冲。否則的話父類初始化器的最后是不可以調(diào)self 的垂睬,因為此時子類的存儲屬性還沒有初始化
- 子類初始化器、父類初始化器、使用屬性這個順序不能變
- 繼承體系中所有的指定初始化器的末尾都可以使用屬性
總結(jié):
- 先在堆內(nèi)創(chuàng)建空間驹饺,之后進行初始化钳枕,初始化完成后才可以使用該實例
- 創(chuàng)建空間后其值是以前空間殘留的,因此后面必須先初始化才能使用赏壹,否則就會有安全問題
- 指定初始化器是縱向的鱼炒,便捷初始化器是橫向的
- 子類調(diào)用初始化器進行初始化的過程中,初始化是從下到上蝌借,定制實例是從上到下
2.3.2 安全檢查
安全檢查就是檢查是否遵守初始化器的規(guī)則
檢查內(nèi)容:
1昔瞧、指定初始化器必須保證在調(diào)用父類初始化器之前,其所在類定義的所有存儲屬性都要完成初始化
2菩佑、指定初始化器必須先調(diào)用父類初始化器自晰,然后才能為繼承的屬性設(shè)置新值
3、便捷初始化器必須先調(diào)用同類中的其他初始化器稍坯,然后再為任意屬性設(shè)置新值
4酬荞、初始化器在第一階段初始化完成之前,不能調(diào)用任何實例方法劣光,不能讀取任何實例屬性的值袜蚕,也不能引用self
5、只有在第一階段結(jié)束 后绢涡,才可以使用實例
2.4 初始化器的重寫和繼承
初始化器的指定初始化器和便捷初始化器在是重寫和繼承中是有區(qū)別的
規(guī)則:
- 默認情況下子類會繼承父類的指定初始化器和便捷初始化器
- 但如果子類自己有指定初始化器牲剃,基于調(diào)用規(guī)則考慮,子類無法繼承父類的初始化器雄可。
- 因為子類指定初始化器必須先自己初始化再調(diào)用父類指定初始化器
- 子類可以重寫父類的指定初始化器為指定初始化器或便捷初始化器
- 基于調(diào)用規(guī)則考慮凿傅,如果重寫為便捷初始化器,函數(shù)體內(nèi)依然要調(diào)用本類的指定初始化器
- 重寫父類的指定初始化器為指定初始化器数苫,也要加上自己的存儲屬性的初始化
- 因為這里的重寫僅僅是重寫聪舒,調(diào)用規(guī)則還是要考慮的
- 當子類不繼承父類的初始化器時,只能重寫父類的指定初始化器虐急,無法重寫父類的便捷初始化器
- 調(diào)用規(guī)則中知道子類可以調(diào)用父類的指定初始化器箱残,但是不能調(diào)用父類的便捷初始化器
- 既然無法調(diào)用父類的便捷初始化器,那么重寫也就無從談起了
- 如果子類全部繼承或重寫了父類的指定初始化器止吁,那么會繼承父類的便捷初始化器被辑。因為此時子類也有父類的指定初始化器了
- 子類有沒有定義新屬性與這些規(guī)則都沒有關(guān)系,關(guān)系僅僅在于子類是否有指定初始化器
理解:
- 便捷初始化器能否繼承依賴于子類與父類的指定初始化器是否一樣
- 因為便捷初始化器來自于本類的指定初始化器
- 如果本類的初始化器與父類的一樣 敬惦,那么就可以繼承
- 如果本類的初始化器與父類的不一樣 盼理,那么就不可以繼承
- 子類是否繼承父類指定初始化器,依賴于子類是否自定義指定初始化器
- 因為調(diào)用規(guī)則來說子類的指定初始化器必須先初始化自己俄删,再調(diào)用父類指定初始化器
- 子類都可以重寫父類的指定初始化器為指定初始化器或便捷初始化器宏怔,但是不能重寫父類的便捷初始化器
- 因為指定初始化器是縱向調(diào)用奏路,便捷初始化器是橫向調(diào)用
- 而且這里的重寫也僅僅是普通的重寫,調(diào)用規(guī)則仍然適用,調(diào)用規(guī)則來說不能調(diào)用父類的便捷初始化器
代碼:
/*
3臊诊、初始化器的重寫和繼承
*/
func test8() {
class Person {
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
init() {
self.age = 0
self.name = ""
}
convenience init(age: Int) {
self.init(age: age, name: "")
}
convenience init(name: String) {
self.init(age: 0, name: name)
}
}
class Student : Person {
//重寫或繼承父類的指定初始化器鸽粉,子類也會繼承父類的便捷初始化器
override init(age: Int, name: String) {
super.init(age: age, name: name)
}
override init() {
super.init(age: 0, name: "")
}
}
var stu = Student(age: 10, name: "WY")
var stu1 = Student()
var stu2 = Student(age: 20)
var stu3 = Student(name: "wenyi")
}
說明:
- 可以將父類的指定初始化器重寫為指定初始化器,也可以重寫為便捷初始化器
- 只要是重寫或繼承父類的指定初始化器妨猩,那么子類就可以繼承父類的便捷初始化器了
2.5 必需初始化器
用required修飾指定初始化器潜叛,其他所有子類必須實現(xiàn)該初始化器
代碼:
/*
4、必需初始化器
*/
func test9() {
class Person {
var age : Int
required init() {
self.age = 0
}
init(age: Int) {
self.age = age
}
}
class Student: Person {
var no : Int
//正常初始化器規(guī)則
init(no: Int) {
self.no = no
super.init(age: 0)
}
//重寫父類的required初始化器
required init() {
self.no = 0
super.init()
}
}
}
說明:
- 必需初始化器只要加上required
- 該初始化器必須被其所有子類的對象所執(zhí)行
- 實現(xiàn)方式包括繼承或重寫
- 如果子類重寫了required初始化器壶硅,也必須加上required威兜,不用加override
2.6 可失敗初始化器
初始化器創(chuàng)建失敗返回nil就叫做可失敗初始化器,類庐椒、結(jié)構(gòu)體椒舵、枚舉中的都可以使用Init?來定義可失敗初始化器
2.6.1 基本使用
代碼:
class Person {
var name: String
init?(name: String){
if name.isEmpty {
return nil
}
self.name = name
}
}
說明:
- 可以看到判斷字符串為空,返回nil约谈,也就是沒有認為該實例初始化失敗笔宿,該對象為nil
- 很明顯,既然可以返回nil棱诱,那么可失敗初始化器返回的是一個可選項
初始化器的使用
var person: Person? = Person()
var age = person?.age()
var name = person?.name
說明:
1泼橘、這里的person是可選項,因此我們?nèi)フ{(diào)用age()時不能直接使用person調(diào)用迈勋。
2炬灭、如果使用person!來調(diào)用,有可能出現(xiàn)person為nil的情況靡菇,此時nil調(diào)用age()直接崩潰
3重归、因此使用person?來調(diào)用,如果nil就不會調(diào)用age()厦凤,直接返回nil鼻吮,如果不為nil才會調(diào)用age()
4、由此也可以看到age有可能被賦值為nil较鼓,因此age只能是個可選項椎木。person?.age()得到的是一個可選項
隱式解包:
class Person {
var name: String
init!(name: String){
if name.isEmpty {
return nil
}
self.name = name
}
convenience init() {
self.init(name: "")
}
}
說明:
- 可失敗初始化器返回的對象是可選項,因此在使用時必須要解包才能用
- 我們可以直接使用init!來隱式解包
- 但是通過實際調(diào)用發(fā)現(xiàn)這樣寫也需要解包才能使用
- 因此可以用在這里博烂,便捷初始化器調(diào)用指定初始化器拓哺,此時隱式解包就起到作用了,不用手動解包
- 當然此時會有隱患脖母,所以最好不要這樣用
2.6.2 可選鏈
在調(diào)用鏈中如果有一個可選項,那么這調(diào)用鏈就是可選鏈闲孤,因為可選項可能為nil谆级,那么其結(jié)果就有可能為nil烤礁,也就必須是個可選項了
代碼:
/*
6、可選鏈
*/
func test11() {
class Car {
var price = 0
}
class Dog {
var weight = 0
}
class Person {
var name: String = ""
var dog: Dog = Dog()
var car: Car? = Car()
func age() -> Int { 18 }
func eat() { print("Person eat") }
subscript(index: Int) -> Int { index }
}
var person: Person? = Person()//拿到可選項對象
var age1 = person!.age() // Int 自動解包肥照,nil也會調(diào)用age()
var age2 = person?.age() // Int? 自動解包脚仔,nil時直接返回nil
var name = person?.name // String? 自動解包
var index = person?[6] // Int? 獲取下標,后面會講到舆绎,本質(zhì)也是一個函數(shù)
var dog = person?.dog // Dog?
var weight = person?.dog.weight // Int? 可選鏈鲤脏,有一個為nil就直接返回nil
var price = person?.car?.price // Int?
}
說明:
- 如果前邊的可選項為nil,那么該實例不再調(diào)用方法吕朵、下標猎醇、屬性,而是直接返回nil
- 如果可選項不為nil努溃,該實例調(diào)用方法硫嘶、下標、屬性梧税,結(jié)果會包裝成可選項沦疾,這是因為這個變量有可能為nil,所以就只能為可選項了
- 如果結(jié)果本來就是可選項第队,將不再包裝哮塞,實例調(diào)用Car會包裝可選項,但是這里的Car得到的本來就是一個可選項凳谦,所以并不會再次包裝了
- 可選項調(diào)用方法屬性等得到的一定是一個可選項忆畅,而這個可選項可以再次調(diào)用方法屬性等,這樣形成的一個鏈就是可選鏈晾蜘,這個鏈中只要有一處為nil邻眷,那么它就直接返回nil,不再執(zhí)行了剔交。
可選鏈的使用:
代碼:
//數(shù)組調(diào)用
var scores = ["WY":[1,2,3],"wenyi":[4,5,6]]
scores["WY"]?[0] = 10
scores["wenyi"]?[2] += 10
scores["wenyiya"]?[0] = 10
//函數(shù)調(diào)用
var dict: [String : (Int, Int) -> Int] = {
"sum" : (+),
"diffenrence" : (-)
}
var result = dict["sum"]?(10,20)
//可選項賦值
var num1: Int? = 5
num1? = 10
var num2: Int? = nil
num2? = 10
說明:
- 數(shù)組調(diào)用中肆饶,先得到字典中的數(shù)組,再獲取數(shù)組中的值岖常,兩次調(diào)用形成了可選鏈驯镊,如果傳入的key值不存在,那么就會返回nil竭鞍。而不再會執(zhí)行[0]了
- 函數(shù)調(diào)用中板惑,拿到sum函數(shù),之后再執(zhí)行sum函數(shù)
- (+)和(-)是其實是函數(shù)偎快,這里編譯器會識別出我們想要進行加法和減法運算
- 在可選鏈中冯乘,只要出現(xiàn)了一個可選項,那么最后得出的結(jié)果就是可選項晒夹,這個很容易理解裆馒,因為前一個值得到的是可選項姊氓,那么它就有可能是nil,它為nil喷好,那么后面都不會執(zhí)行翔横,最終會返回nil。因此得到的最終結(jié)果就有可能為nil梗搅,所以必須是可選項
- 可選項賦值中禾唁,可以直接賦值給num1,也可以賦值給num1?无切,如果賦值給num1?荡短,那么就是可選項,這樣做有一個好處就是如果num1為nil订雾,那么不會再執(zhí)行肢预,可以減少無效性能損耗
2.7 總結(jié)
注意:
- 不允許同時定義參數(shù)標簽、參數(shù)個數(shù)洼哎、參數(shù)類型相同的可失敗初始化器和非可失敗初始化器烫映,重載與是不是可失敗的沒有關(guān)系
- 可失敗初始化器可以調(diào)用非可失敗初始化器,非可失敗初始化器調(diào)用可失敗初始化器需要進行解包
- 如果初始化器調(diào)用一個可失敗初始化器導致初始化失敗噩峦,那么整個初始化過程都失敗锭沟,并且之后的代碼都停止執(zhí)行。也就是說只要有一個為nil识补,編譯器就不會執(zhí)行后邊的代碼
- 可以用一個非可失敗初始化器重寫一個可失敗初始化器族淮,但反過來是不行的
- 在swift中沒有返回值的函數(shù)調(diào)用,也是可以接收一個變量的凭涂,因為在Swift中Void其實是空元組祝辣,并非真正的空
- 父類的屬性在它自己的初始化器中賦值不會觸發(fā)屬性觀察器,但在子類的初始化器中賦值會觸發(fā)屬性觀察器
總結(jié):
- Swift對于初始化器的安全做了很多措施切油,包括強制分為指定初始化器和便捷初始化器蝙斜、兩段式初始化、安全檢查
- 繼承關(guān)系中所有的初始化器保證都能初始化所有實例
- 初始化的過程是從下到上的澎胡,實例使用的過程是從上到下的
- 同類的兩個指定初始化器孕荠,是不能互相調(diào)的。指定初始化器是縱向的攻谁,便捷初始化器是橫向的
- 必需初始化器強制讓子類實現(xiàn)該初始化器稚伍,可以通過重寫或繼承
- 可失敗初始化器返回的對象是可選項
- 可選鏈中只要有一個nil就返回nil
3、方法
3.1 簡單定義
代碼:
/*
1戚宦、方法
*/
func test12() {
//方法定義
class Car {
static var cout = 0
init() {
Car.cout += 1
}
static func getCount() -> Int { cout }
}
let c0 = Car()
let c1 = Car()
let c2 = Car()
print(Car.getCount()) // 3
}
說明:
- 有類型方法和實例方法个曙,類型方法通過static或class來定義的,如果是結(jié)構(gòu)體或枚舉不能用class定義
- 實例方法只能使用實例屬性受楼,類型方法既可以使用實例屬性也可以使用類型屬性
- 實例方法中的self表示當前實例困檩,類型方法中的self表示當前實例或當前類型
3.2 關(guān)鍵字
3.2.1 mutating
結(jié)構(gòu)體和枚舉是值類型祠挫,默認情況下,值類型的屬性不能被自身的實例方法修改悼沿,通過在func關(guān)鍵字前加mutating可以允許這種修改行為(類本身就可以直接進行修改的)
代碼:
//關(guān)鍵字mutating
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(deltaX: Double, deltaY: Double) {
x += deltaX
y += deltaY
// self = Point(x: x + deltaX, y: y + deltaY)
}
}
說明:
- 直接在func關(guān)鍵字前加mutating就可以了
3.2.2 @discardableResult
如果一個函數(shù)/方法有返回值,但是我們沒有將其接收骚灸,調(diào)用時會報黃色警告,此時可以在func前面加上@discardableResult就可以消除警告
代碼:
//關(guān)鍵字@discardableResult
struct Point2 {
var x = 0.0, y = 0.0
@discardableResult mutating func moveX(deltaX: Double) -> Double {
x += deltaX
return x
}
}
var p = Point2()
p.moveX(deltaX: 10)
4糟趾、下標
Swift提供了下標函數(shù),使用subscript可以給任意類型增加下標功能甚牲,這里的下標就類似于數(shù)組的下標使用义郑,但比數(shù)組的下標操作可以更加豐富。我們通過下標函數(shù)可以更方便的進行數(shù)據(jù)操作
4.1 基本使用
代碼:
class Point {
var x = 0.0, y = 0.0
//index就是傳入的下標
//在函數(shù)內(nèi)對不同的下標進行不同操作
//也可以只有g(shù)et方法
subscript(index: Int) -> Double {
set {
if index == 0 {
x = newValue
} else if index == 1 {
y = newValue
}
}
get {
if index == 0 {
return x
} else if index == 1 {
return y
}
return 0
}
}
}
var p = Point()
p[0] = 11.1
p[1] = 22.2
print(p.x) // 11.1
print(p.y) // 22.2
print(p[0]) // 11.1
print(p[1]) // 22.2
//設(shè)置參數(shù)標簽
class Point2 {
var x = 0.0, y = 0.0
subscript(index i: Int) -> Double {
if i == 0 {
return x
} else if i == 1 {
return y
}
return 0
}
}
//類方法的下標
class Sum {
static subscript(v1: Int, v2: Int) -> Int {
return v1 + v2
}
}
print(Sum [10, 20]) // 30
說明:
- subscript語法類似于方法丈钙、計算屬性非驮,本質(zhì)上也就是一個方法
- 這個subscript函數(shù)可以傳入下標,在函數(shù)體內(nèi)又可以通過傳入的值來判斷進行操作雏赦,只不過在調(diào)用這個函數(shù)時就可以通過下標來調(diào)用
- 需要通過set方法劫笙、get方法來實現(xiàn),也可以沒有set方法星岗,只有g(shù)et方法
- 在這個函數(shù)中也可以設(shè)置 參數(shù)標簽填大,這樣在設(shè)置下標的時候可以更加具體
- 下標方法可以是類型方法
4.2 類和結(jié)構(gòu)體作為下標函數(shù)的返回的區(qū)別
4.2.1 類作為下標函數(shù)返回
代碼:
//定義類
class Point {
var x = 0, y = 0
}
class PointManager {
var point = Point()
subscript(index: Int) -> Point {
get { point }
}
}
var pm = PointManager()
pm[0].x = 11
pm[0].y = 22
// Point(x: 11, y: 22)
print(pm[0])
// Point(x: 11, y: 22)
print(pm.point)
說明:
- 如果返回的是class,那么即使只有g(shù)et方法俏橘,也可以直接賦值允华。因為它是引用類型
- pm[0]就拿到了point結(jié)構(gòu)體,此時可以直接賦值給結(jié)構(gòu)體的存儲屬性
4.2.2 結(jié)構(gòu)體作為下標函數(shù)返回
代碼:
//定義結(jié)構(gòu)體
struct Point {
var x = 0, y = 0
}
class PointManager {
var point = Point()
subscript(index: Int) -> Point {
set { point = newValue }
get { point }
}
}
var pm = PointManager()
pm[0].x = 11
pm[0].y = 22
// Point(x: 11, y: 22)
print(pm[0])
// Point(x: 11, y: 22)
print(
說明:
- 如果只有g(shù)et方法寥掐,是不能給pm[0]賦值的
- 2靴寂、當有set方法時,此時的賦值就和下面的那條等價了
總結(jié):
- 對于結(jié)構(gòu)體來說召耘,point作為值類型百炬,pm[0]拿出來的point是拷貝出來的值,所以是無法修改內(nèi)部的Point值的怎茫。所以只能使用set/get方法對內(nèi)部的point來設(shè)置
- 對于類來說收壕,在外面進行修改是可以修改下標函數(shù)的里對象的值的,所以是可以直接進行修改的
4.3 接收多個參數(shù)的下標
注意一下寫法就可以轨蛤,和二維數(shù)組一樣
代碼:
/*
4蜜宪、多維下標
*/
func test15() {
class Grid {
var data = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8]
]
subscript(row: Int, column: Int) -> Int {
set {
guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
return
}
data [row][column] = newValue
}
get {
guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
return 0
}
return data [row][column]
}
}
}
let grid = Grid()
grid[0, 1] = 77
grid[1, 2] = 88
grid[2, 0] = 99
print(grid.data)
}
5、總結(jié)
- 屬性可以分為存儲屬性祥山、計算屬性圃验、還可以分為類屬性和實例屬性
- 存儲屬性占用實例內(nèi)存,可以添加屬性觀察器缝呕,屬性觀察器本質(zhì)是函數(shù)
- 計算屬性不占用實例內(nèi)存澳窑,本質(zhì)是函數(shù)
- 初始化器分為指定初始化器和便捷初始化器
- 對于調(diào)用規(guī)則的理解斧散,可以通俗的認為指定初始化器是類的默認初始化器,它可以在繼承體系中溝通父類子類的屬性摊聋,因此是縱向的鸡捐。而便捷初始化器是為了調(diào)用時更加方便,因此它必須要調(diào)用默認的指定初始化器麻裁。
- 初始化器用來初始化實例箍镜,Swift為了確保初始化的安全提供了兩段式初始化過程和安全檢查來初始化
- 過程為:子類的便捷初始化器-> 子類的指定初始化器 -> 子類屬性的初始化 -> 父類屬性的初始化 -> 父類實例的使用 -> 子類實例的使用
- 方法沒有特殊性,只是增加了下標函數(shù)