Swift 結構體和類
結構體
在 Swift 標準庫中芭商,絕大多數(shù)的公開類型都是結構體搀缠,而枚舉和類只占很小一部分
比如Bool艺普、Int、Double岸浑、 String缴罗、Array面氓、Dictionary
等常見類型都是結構體
1. 初始化器
所有的結構體都有一個編譯器自動生成的初始化器(initializer,初始化方法掘譬、構造器呻拌、構造方法)
struct Date {
var year: Int
var month: Int
var day: Int
}
// 可以傳入所有成員值,用以初始化所有成員(存儲屬性靴拱,Stored Property)
var date = Date(year: 2019, month: 06, day: 29)
如果給屬性設置可選變量,則會生成多個構造器(初始化器)
struct Point {
var x: Int?
var y: Int?
}
var p1 = Point(x: 10, y: 10)
var p2 = Point(y: 10)
var p3 = Point(x: 10)
var p4 = Point()
類
1. 類的初始化器
- 類的定義和結構體類似猾普,但編譯器并沒有為類自動生成可以傳入成員值的初始化器
- 如果類的所有成員都在定義的時候指定了初始值初家,編譯器會為類生成無參的初始化器
class Point {
var x: Int = 0
var y: Int = 0
}
var point = Point()
2. 結構體與類的本質區(qū)別
結構體是值類型(枚舉也是值類型)乌助,類是引用類型(指針類型)
值類型
- 值類型賦值給var他托、let或者給函數(shù)傳參赏参,是直接將所有內容拷貝一份
- 類似于對文件進行copy浙芙、paste操作嗡呼,產生了全新的文件副本。屬于深拷貝(deep copy)
- 在Swift標準庫中揍很,為了提升性能万伤,String敌买、Array、Dictionary聋庵、Set采取了Copy On Write的技術
- 比如僅當有“寫”操作時祭玉,才會真正執(zhí)行拷貝操作
- 對于標準庫值類型的賦值操作春畔,Swift 能確保最佳性能律姨,所有沒必要為了保證最佳性能來避免賦值
- 建議:不需要修改的,盡量定義成let
引用類型
- 引用賦值給var铺韧、let或者給函數(shù)傳參缓淹,是將內存地址拷貝一份
- 類似于制作一個文件的替身(快捷方式讯壶、鏈接),指向的是同一個文件立轧。屬于淺拷貝(shallow copy)
Swift 屬性
1. 存儲屬性&計算屬性
Swift中跟實例對象相關的屬性可以分為2大類
存儲屬性(Stored Property)
- 類似于成員變量這個概念
- 存儲在實例對象的內存中
- 結構體氛改、類可以定義存儲屬性
- 枚舉不可以定義存儲屬性
計算屬性(Computed Property)
- 本質就是方法(函數(shù))
- 不占用實例對象的內存
- 枚舉比伏、結構體赁项、類都可以定義計算屬性
struct Circle {
// 存儲屬性
var radius: Double
// 計算屬性
var diameter: Double {
set {
radius = newValue / 2
}
get {
return radius * 2
}
}
}
2. 計算屬性
set傳入的新值默認叫做newValue悠菜,也可以自定義
struct Circle {
var radius: Double
var diameter: Double {
set(newDiameter) {
radius = newDiameter / 2
}
get {
radius * 2 }
}
}
只讀計算屬性:只有get,沒有set
struct Circle {
var radius: Double
var diameter: Double {
get {
radius * 2
}
}
}
- 定義計算屬性只能用var摩窃,不能用let
- let代表常量:值是一成不變的
- 計算屬性的值是可能發(fā)生變化的(即使是只讀計算屬性)
3. 枚舉rawValue原理
枚舉原始值rawValue的本質是:只讀計算屬性
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 }
}
}
4. 延遲存儲屬性(Lazy Stored Property)
使用lazy可以定義一個延遲存儲屬性猾愿,在第一次用到屬性的時候才會進行初始化
- lazy屬性必須是var德玫,不能是let
- let必須在實例對象的初始化方法完成之前就擁有值
- 如果多條線程同時第一次訪問lazy屬性
- 無法保證屬性只被初始化1次
class PhotoView {
lazy var image: Image = {
let url = "https://www.../xx.png"
let data = Data(url: url)
return Image(data: data)
}()
}
5. 屬性觀察器(Property Observer)
可以為非lazy的var存儲屬性設置屬性觀察器
struct Circle {
var radius: Double {
willSet {
print("willSet", newValue)
}
didSet {
print("didSet", oldValue, radius)
}
}
init() {
self.radius = 1.0
print("Circle init!")
}
}
- willSet會傳遞新值宰僧,默認叫newValue
- didSet會傳遞舊值琴儿,默認叫oldValue
- 在初始化器中設置屬性值不會觸發(fā)willSet和didSet
6.類型屬性(Type Property)
嚴格來說,屬性可以分為
實例屬性(Instance Property): 只能通過實例對象去訪問
- 存儲實例屬性(Stored Instance Property):存儲在實例對象的內存中显熏,每個實例對象都有1份
- 計算實例屬性(Computed Instance Property)
類型屬性(Type Property):只能通過類型去訪問
- 存儲類型屬性(Stored Type Property):整個程序運行過程中喘蟆,就只有1份內存(類似于全局變量)
- 計算類型屬性(Computed Type Property)
可以通過static定義類型屬性 p如果是類,也可以用關鍵字class
struct Car {
static var count: Int = 0
init() {
Car.count += 1
}
}
不同于存儲實例屬性港谊,你必須給存儲類型屬性設定初始值
- 因為類型沒有像實例對象那樣的init初始化器來初始化存儲屬性
存儲類型屬性默認就是lazy歧寺,會在第一次使用的時候才初始化
- 就算被多個線程同時訪問棘脐,保證只會初始化一次
- 存儲類型屬性可以是let
枚舉類型也可以定義類型屬性(存儲類型屬性蛀缝、計算類型屬性)
7. 單例模式
通過 類型屬性
+let
+private
來寫單例;
public class FileManager {
public static let shared = {
// ....
// ....
return FileManager()
}()
private init() { }
}
Swift 方法
枚舉、結構體蕴潦、類都可以定義實例方法潭苞、類型方法
- 實例方法(Instance Method):通過實例對象調用
- 類型方法(Type Method):通過類型調用真朗,用static或者class關鍵字定義
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
self
1.在實例方法中代表實例對象
2.在類型方法中代表類型
在類型方法static func getCount中
cout等價于self.cout遮婶、Car.self.cout、Car.cout
1.mutating
結構體和枚舉是值類型蹦骑,默認情況下臀防,值類型的屬性不能被自身的實例方法修改
- 在func關鍵字前加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)
} }
2. @discardableResult
在func前面加個@discardableResult袱衷,可以消除:函數(shù)調用后返回值未被使用的警告
struct Point {
var x = 0.0, y = 0.0
@discardableResult mutating
func moveX(deltaX: Double) -> Double {
x += deltaX
return x }
}
var p = Point()
p.moveX(deltaX: 10)
@discardableResult
func get() -> Int {
return 10 }
get()
Swift 下標
使用subscript
可以給任意類型(枚舉致燥、結構體、類)增加下標功能辐益,有些地方也翻譯為:下標腳本
- subscript的語法類似于實例方法智政、計算屬性,本質就是方法(函數(shù))
class Point {
var x = 0.0, y = 0.0
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
- subscript中定義的返回值類型決定了
- get方法的返回值類型
- set方法中newValue的類型
- subscript可以接受多個參數(shù),并且類型任意
1. 下標的細節(jié)
- subscript可以沒有set方法疾忍,但必須要有get方法
class Point {
var x = 0.0, y = 0.0
subscript(index: Int) -> Double {
get {
if index == 0 {
return x
} else if index == 1 {
return y }
return 0 }
} }
- 如果只有get方法一罩,可以省略get
class Point {
var x = 0.0, y = 0.0
subscript(index: Int) -> Double {
if index == 0 {
return x
} else if index == 1 {
return y
}
return 0
}
}
- 可以設置參數(shù)標簽
class Point {
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
2. 接收多個參數(shù)的下標
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] }
} }
var grid = Grid()
grid[0, 1] = 77
grid[1, 2] = 88
grid[2, 0] = 99
print(grid.data)
Swift 繼承
- 值類型(枚舉聂渊、結構體)不支持繼承汉嗽,只有類支持繼承
- 沒有父類的類找蜜,稱為:基類 ,Swift并沒有像OC洗做、Java那樣的規(guī)定:任何類最終都要繼承自某個基類
- 子類可以重寫父類的下標、方法撰筷、屬性畦徘,重寫必須加上override關鍵字
class Animal {
var age = 0
}
class Dog : Animal {
var weight = 0
}
class ErHa : Dog {
var iq = 0
}
1. 重寫實例方法旧烧、下標
class Animal {
func speak() {
print("Animal speak")
}
subscript(index: Int) -> Int {
return index
} }
var anim: Animal
anim = Animal()
// Animal speak
anim.speak()
// 6
print(anim[6])
class Cat : Animal {
override func speak() {
super.speak()
print("Cat speak")
}
override subscript(index: Int) -> Int {
return super[index] + 1
} }
anim = Cat()
// Animal speak
// Cat speak
anim.speak()
// 7
print(anim[6])
- 被class修飾的類型方法掘剪、下標,允許被子類重寫
- 被static修飾的類型方法廉赔、下標蜡塌,不允許被子類重寫
2. 重寫屬性
- 子類可以將父類的屬性(存儲、計算)重寫為計算屬性
- 子類不可以將父類屬性重寫為存儲屬性
- 只能重寫var屬性劳曹,不能重寫let屬性
- 重寫時铁孵,屬性名房资、類型要一致
- 子類重寫后的屬性權限 不能小于 父類屬性的權限
- 如果父類屬性是只讀的轰异,那么子類重寫后的屬性可以是只讀的、也可以是可讀寫的
- 如果父類屬性是可讀寫的婴削,那么子類重寫后的屬性也必須是可讀寫的
3. 屬性觀察器
可以在子類中為父類屬性(除了只讀計算屬性馆蠕、let屬性)增加屬性觀察器
class Circle {
var radius: Int = 1
}
class SubCircle : Circle {
override var radius: Int {
willSet {
print("SubCircle willSetRadius", newValue) }
didSet {
print("SubCircle didSetRadius", oldValue, radius)
} }
}
var circle = SubCircle()
// SubCircle willSetRadius 10 // SubCircle didSetRadius 1 10 circle.radius = 10
class Circle {
var radius: Int = 1 {
willSet {
print("Circle willSetRadius", newValue)
} didSet {
print("Circle didSetRadius", oldValue, radius) }
}
}
class SubCircle : Circle {
override var radius: Int {
willSet {
print("SubCircle willSetRadius", newValue)
} didSet {
print("SubCircle didSetRadius", oldValue, radius) }
}
}
class Circle {
var radius: Int {
set {
print("Circle setRadius", newValue)
} get {
print("Circle getRadius")
return 20 }
}
}
class SubCircle : Circle {
override var radius: Int {
willSet {
print("SubCircle willSetRadius", newValue)
} didSet {
print("SubCircle didSetRadius", oldValue, radius) }
}
}
4. final
- 被final修飾的方法互躬、下標颂郎、屬性乓序,禁止被重寫
- 被final修飾的類,禁止被繼承
Swift 初始化
1. 初始化器
類寄雀、結構體陨献、枚舉都可以定義初始化器
類有2種初始化器: 指定初始化器(designated initializer)
、便捷初始化器(convenience initializer)
// 指定初始化器
init(parameters) {
statements
}
// 便捷初始化器
convenience init(parameters) {
statements
}
規(guī)則:
- 每個類至少有一個指定初始化器沮协,指定初始化器是類的主要初始化器
- 默認初始化器總是類的指定初始化器
- 類偏向于少量指定初始化器卓嫂,一個類通常只有一個指定初始化器
初始化器的相互調用規(guī)則
- 指定初始化器必須從它的直系父類調用指定初始化器
- 便捷初始化器必須從相同的類里調用另一個初始化器
- 便捷初始化器最終必須調用一個指定初始化器
// 便捷初始化器最終必須調用一個 指定初始化器,保證安全,保證所有屬性都被初始化
class Person {
var name: String
var age: Int
var height: Int {
get {
return age*10;
}
}
// 指定初始化器
init(name: String, age: Int) {
self.name = name;
self.age = age;
}
// 便捷初始化器
convenience init(name: String){
self.init(name: name, age: 0);
}
// 便捷初始化器
convenience init (age: Int) {
self.init(name: "", age: age);
}
// 便捷初始化器
convenience init(){
self.init(name: "", age: 0);
}
}
var ppp = Person(age: 10);
var ppp1 = Person(name: "alex");
var PPP2 = Person(name: "alex", age: 10);
初始化器的相互調用
2. 兩段式初始化
Swift在編碼安全方面是煞費苦心行瑞,為了保證初始化過程的安全蘑辑,設定了兩段式初始化
坠宴、 安全檢查
兩段式初始化
-
第1階段:初始化所有存儲屬性
- 外層調用指定\便捷初始化器
- 分配內存給實例喜鼓,但未初始化
- 指定初始化器確保當前類定義的存儲屬性都初始化
- 指定初始化器調用父類的初始化器,不斷向上調用衔肢,形成初始化器鏈
-
第2階段:設置新的存儲屬性值
- 從頂部初始化器往下庄岖,鏈中的每一個指定初始化器都有機會進一步定制實例
- 初始化器現(xiàn)在能夠使用self(訪問、修改它的屬性角骤,調用它的實例方法等等)
- 最終隅忿,鏈中任何便捷初始化器都有機會定制實例以及使用self
3. 安全檢查
- 指定初始化器必須保證在調用父類初始化器之前,其所在類定義的所有存儲屬性都要初始化完成
- 指定初始化器必須先調用父類初始化器邦尊,然后才能為繼承的屬性設置新值
- 便捷初始化器必須先調用同類中的其它初始化器背桐,然后再為任意屬性設置新值
- 初始化器在第1階段初始化完成之前蝉揍,不能調用任何實例方法链峭、不能讀取任何實例屬性的值,也不能引用self n 直到第1階段結束又沾,實例才算完全合法
4. 重寫
- 當重寫父類的指定初始化器時弊仪,必須加上override(即使子類的實現(xiàn)是便捷初始化器)
- 如果子類寫了一個匹配父類便捷初始化器的初始化器,不用加上override
- 因為父類的便捷初始化器永遠不會通過子類直接調用杖刷,因此励饵,嚴格來說,子類無法重寫父類的便捷初始化器
5.自動繼承
- 如果子類沒有自定義任何指定初始化器滑燃,它會自動繼承父類所有的指定初始化器
- 如果子類提供了父類所有指定初始化器的實現(xiàn)(要么通過方式1繼承役听,要么重寫)
- 子類自動繼承所有的父類便捷初始化器
- 就算子類添加了更多的便捷初始化器,這些規(guī)則仍然適用
- 子類以便捷初始化器的形式重寫父類的指定初始化器,也可以作為滿足規(guī)則2的一部分
6.required
- 用required修飾指定初始化器禾嫉,表明其所有子類都必須實現(xiàn)該初始化器(通過繼承或者重寫實現(xiàn))
- 如果子類重寫了required初始化器灾杰,時也必須加上required,不用加override
class Person {
required init() { }
init(age: Int) { }
}
class Student: Person {
required init() {
super.init()
}
}
7.屬性觀察器
父類的屬性在它自己的初始化器中賦值不會觸發(fā)屬性觀察器熙参,但在子類的初始化器中賦值會觸發(fā)屬性觀察器
class Person {
var age: Int {
willSet {
print("willSet", newValue)
} didSet {
print("didSet", oldValue, age)
}
} init() {
self.age = 0 }
}
class Student: Person {
override init() {
super.init()
self.age = 1 }
}
// willSet 1
// didSet 0 1
var stu = Student()
8.可失敗初始化器
類艳吠、結構體、枚舉都可以使用init?定義可失敗初始化器
class Person {
var name: String
init?(name: String) {
if name.isEmpty {
return nil
}
self.name = name
}
}
- 不允許同時定義參數(shù)標簽孽椰、參數(shù)個數(shù)昭娩、參數(shù)類型相同的可失敗初始化器和非可失敗初始化器
- 可以用init!定義隱式解包的可失敗初始化器
- 可失敗初始化器可以調用非可失敗初始化器,非可失敗初始化器調用可失敗初始化器需要進行解包
- 如果初始化器調用一個可失敗初始化器導致初始化失敗黍匾,那么整個初始化過程都失敗栏渺,并且之后的代碼都停止執(zhí)行
- 可以用一個非可失敗初始化器重寫一個可失敗初始化器,但反過來是不行的
9.反初始化器(deinit)
deinit叫做反初始化器锐涯,類似于C++的析構函數(shù)磕诊、OC中的dealloc方法
- 當類的實例對象被釋放內存時,就會調用實例對象的deinit方法
class Person {
deinit {
print("Person對象銷毀了")
}
}
- deinit不接受任何參數(shù)纹腌,不能寫小括號霎终,不能自行調用
- 父類的deinit能被子類繼承
- 子類的deinit實現(xiàn)執(zhí)行完畢后會調用父類的deinit
Swift 可選鏈
可選鏈是一個調用和查詢可選屬性、方法和下標的過程升薯,它可能為 nil 莱褒。如果可選項包含值,屬性涎劈、方法或者下標的調用成功广凸;如果可選項是 nil ,屬性蛛枚、方法或者下標的調用會返回 nil 谅海。多個查詢可以鏈接在一起,如果鏈中任何一個節(jié)點是 nil 蹦浦,那么整個鏈就會得體地失敗胁赢。
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 {
return 18
}
func eat() {
print("Person eat")
}
subscript(index: Int) -> Int {
return index
}
}
var person: Person? = Person()
var age1 = person!.age() // Int
var age2 = person?.age() // Int?
var name = person?.name // String?
var index = person?[6] // Int?
func getName() -> String { return "jack" }
// 如果person是nil,不會調用getName()
person?.name = getName()
- 如果可選項為nil白筹,調用方法智末、下標、屬性失敗徒河,結果為nil
- 如果可選項不為nil系馆,調用方法、下標顽照、屬性成功由蘑,結果會被包裝成可選項
- 如果結果本來就是可選項闽寡,不會進行再次包裝
var dog = person?.dog // Dog?
var weight = person?.dog.weight // Int?
var price = person?.car?.price // Int?
- 多個?可以鏈接在一起
- 如果鏈中任何一個節(jié)點是nil,那么整個鏈就會調用失敗