Swift 結(jié)構(gòu)體同樣擁有函數(shù)和屬性以及協(xié)議等能力,乍一看和class沒什么不同。
// 語法
struct nameStruct {
Definition 1
Definition 2
……
Definition N
}
struct Book {
var name: String
func eat() {
}
}
let book = Book(name: "Apple")
print(book.name) // "Apple"
1. Struct 與 Class 的異同點(diǎn)
| struct | class | |
|---|---|---|
| 傳遞方式 | 傳值 | 引用 |
| 能否繼承 | 不可繼承 | 可以繼承 |
| 協(xié)議、泛型 | 支持 | 支持 |
| 內(nèi)存管理 | 系統(tǒng)自動管理 | 引用計(jì)數(shù) |
| 內(nèi)存位置 | 棧 | 堆 |
| Runtime | 不支持 | 支持 |
| 與OC互動 | 不支持 | 支持 |
| 歸檔 | 不支持 | 支持 |
2. 結(jié)構(gòu)體的使用
結(jié)構(gòu)體的使用和類基本相同,但是也有一些不同。
struct Student {
let name: String
let age: UInt
var math: Int
var english: Int
var chinese: Int
var isAdult: Bool {
return age >= 18
}
}
2.1 修改結(jié)構(gòu)體的屬性值
聲明一個(gè)Student常量,如果我們想要修改math值,將會報(bào)錯(cuò),因?yàn)閟truct被聲明為了常量,它作為一個(gè)整體,屬性也都不允許修改,除非聲明為變量(var)。如果Student是class,則這里允許修改。
let stu = Student.init(name: "linda", age: 15, math: 90, english: 87, chinese: 96)
stu.math = 100 // error: Cannot assign to property: 'stu' is a 'let' constant
var stu2 = Student.init(name: "bob", age: 16, math: 80, english: 70, chinese: 60)
stu2.english = 90 // stu2.english is 90
2.2 結(jié)構(gòu)體的函數(shù)修改自身屬性值
給Student擴(kuò)展兩個(gè)函數(shù),如果函數(shù)想要修改自身屬性值,則必須添加mutating關(guān)鍵字,否則會報(bào)錯(cuò),因?yàn)槟J(rèn)是immutable。
extension Student {
func cheat(math: Int) {
self.math = math // Cannot assign to property: 'self' is immutable
}
mutating func cheat(english: Int) {
self.english = english
}
}
2.4 類包含結(jié)構(gòu)體
結(jié)構(gòu)體作為類的屬性,那么結(jié)構(gòu)體屬性自身遵循結(jié)構(gòu)體的特性,而類自身也不會受到影響。
class Teacher {
let students: [Student]
var salary: Float
init(students: [Student], salary: Float) {
self.students = students
self.salary = salary
}
}
var teacher = Teacher.init(students: [], salary: 10000)
teacher.salary += 1000
teacher.students.append(.init(name: "sam", age: 11, math: 98, english: 78, chinese: 11)) // Cannot use mutating member on immutable value: 'students' is a 'let' constant
teacher = Teacher.init(students: [], salary: 20000)
我們可以修改teacher類的salary和其自身,但是students結(jié)構(gòu)體被定義為常量則不允許修改。
2.4 結(jié)構(gòu)體包含類
結(jié)構(gòu)體包含類,修改屬性中的類的屬性仍然是可行的。但是由于傳值的特性,class的引用特性將會被干擾,每一次復(fù)制都會復(fù)制里面的class,這樣會造成大量的資源浪費(fèi)。
為了解決這種問題,在結(jié)構(gòu)體中包含類的且可能會有復(fù)制操作的,我們需要使用寫時(shí)復(fù)制的方式處理。
isKnownUniquelyReferenced(_:)函數(shù)用于判斷一個(gè)對象是否僅引用了一次。
struct Model<Element: NSMutableCopying> {
private var _model: Element
var model: Element {
mutating get {
if !isKnownUniquelyReferenced(&_model) {
_model = _model.mutableCopy() as! Element
print("copy!")
}
return _model
}
}
init(_ model: Element) {
_model = model
}
}
let str = NSMutableString(string: "1")
var model = Model(str)
// 如若該行代碼被屏蔽,則不會復(fù)制
model.model.append("2") // copy!
即便是這樣,在該結(jié)構(gòu)體被其他結(jié)構(gòu)體所包含時(shí),也可能會出現(xiàn)復(fù)制的問題,盡量不要將類作為結(jié)構(gòu)體的一部分。
3. 何時(shí)使用結(jié)構(gòu)體
結(jié)構(gòu)體的主要目的是用來封裝少量相關(guān)簡單數(shù)據(jù)值,相對于OC一切Model皆Class來說,Swift的Struct更適合邏輯很少的存儲型結(jié)構(gòu)。
struct Person {
let name: String
let age: UInt
var isAdult: Bool {
return age >= 18
}
}
class User: NSCoding {
let id: String
let nickName: String
init(id: String,
nickName: String) {
self.id = id
self.nickName = nickName
}
func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: "id")
aCoder.encode(nickName, forKey: "nickName")
}
required init?(coder aDecoder: NSCoder) {
guard let id = aDecoder.decodeObject(forKey: "id") as? String else { return nil }
guard let nickName = aDecoder.decodeObject(forKey: "nickName") as? String else { return nil }
self.id = id
self.nickName = nickName
}
}
上面定義了兩個(gè)類型,Person struct和 User class。
- Person僅僅存儲了兩個(gè)屬性,并且有一個(gè)簡單的邏輯isAdult,Person的內(nèi)存長度很小,如果不需要?dú)w檔功能并且常用于只讀場景,那么使用struct將會優(yōu)于class。
- User同樣也是兩個(gè)屬性,但是設(shè)計(jì)需要?dú)w檔,那么它必須是class。即便不是歸檔的原因,在開發(fā)中User也可能被多次讀寫和傳遞,使用class可能會更適合一些。
其實(shí)大多數(shù)情況下struct和class沒什么區(qū)別,但是能用struct的地方還是盡量用struct,棧始終要比堆要快一些(由于struct的傳值方式可能會帶來內(nèi)存峰值占用過高的情況,實(shí)際選擇哪一種類型要看具體的應(yīng)用場景)。
4. 系統(tǒng)結(jié)構(gòu)體
- Swift中集合類:
Array,Dictionary,Set均為結(jié)構(gòu)體類型。