关于Swift中Struct,Class和Enum的哪些事儿
前言
Swift type System
Swift是強(qiáng)類型的,盡管只有六種類型。
- 命名類型: protocol, class , struct , enum
 - 復(fù)合類型:tuple, function
 
可能會(huì)有疑問,那些基本類型:Bool,Int,UInt, Float, Double, Character, String, Array, Set, Dictionary, Optional。實(shí)際上他們都是通過命名類型創(chuàng)建的。
Struct Class and Enum 比較
Swift中提供了多種可以結(jié)構(gòu)化存儲(chǔ)數(shù)據(jù)的方式,它們是: struct、enum和
class。Swift標(biāo)準(zhǔn)庫中的絕大多數(shù)類型都是struct,甚至Foundation中的一些類也提供了它們?cè)赟wift中的struct版本,而class和enum只占很少一部分。
Class,Struct and Enum對(duì)比表
| Class | Reference | ? | ? | ? | ? | ? | 
| Struct | Value | ? | ? | ? | ? | ? | 
| Enum | Value | ? | ? | ? | ? | ? | 
共同點(diǎn):
如何抉擇?
通常,在平時(shí)的編程中,按照對(duì)象的生命周期形態(tài),可以把使用的類型分成兩大類:
- 一類必須有明確生命周期的,它們必須被明確的初始化、使用、最后明確的被釋放。例如:文件句柄、數(shù)據(jù)庫連接、線程同步鎖等等。這些類型的初始化和釋放都不是拷貝內(nèi)存這么簡(jiǎn)單,通常,這類內(nèi)容,我們會(huì)選擇使用class來實(shí)現(xiàn)。
 - 另一類,則是沒有那么明顯的生命周期。 例如:整數(shù)、字符串、URL等等。這些對(duì)象一旦被創(chuàng)建之后,就很少被修改,我們只是需要使用這些對(duì)象的值,用完之后,我們也無需為這些對(duì)象的銷毀做更多額外的工作,只是把它們占用的內(nèi)存回收就好了。這類內(nèi)容,通常我們會(huì)選擇使用struct或enum來實(shí)現(xiàn)。
 
Struct
Struct的定義和初始化
 定義結(jié)構(gòu)體  
下面定義了一個(gè)二維空間坐標(biāo)的類型:
這個(gè)結(jié)構(gòu)體包含兩個(gè)名x和y的存儲(chǔ)屬性。存儲(chǔ)屬性是被綁定和存儲(chǔ)在結(jié)構(gòu)體中的常量或變量。
初始化
- 結(jié)構(gòu)體類型的逐一初始化
所有的結(jié)構(gòu)體都有一個(gè)自動(dòng)生成的成員逐一構(gòu)造器 var pointA = Point(x: 10, y: 20) - 默認(rèn)初始化
我們也可以在定義的時(shí)候直接給屬性初始化 struct Point { var x = 0.0 var y = 0.0 } var pointB = Point()使用這種方法,必須給每一個(gè)屬性指定默認(rèn)值。因?yàn)镾wift中要求init方法必須初始化自定義類型每個(gè)屬性。如果無法做到,我們可以自定義逐一初始化方法。
struct Point { var x : Double var y : Double init(_ x : Double = 0.0, y : Double = 0.0) {self.x = xself.y = y } }當(dāng)我們自定義init方法之后,Swift將不會(huì)再自動(dòng)創(chuàng)建逐一初始化方法。
 
Struct 值類型本質(zhì)
var pointB = Point(200, y: 100) var pointC = Point(100, y: 200) {didSet {print("\(pointC)")} } pointC = pointB // Point(x: 200.0, y: 100.0) pointC.x = 200 //Point(x: 200.0, y: 100.0)通過didSet觀察pointC的變化。當(dāng)修改pointC變量值時(shí),控制臺(tái)輸出Point(x: 200.0, y: 100.0), 但是,修改pointC的修改某個(gè)屬性,也會(huì)觸發(fā)didSet。
這就是值語義的本質(zhì):即使字面上修改了pointC變量的某個(gè)屬性,但實(shí)際執(zhí)行的邏輯是重新給pointC賦值一個(gè)新的Point對(duì)象。
為Struct添加方法
給struct添加的方法,默認(rèn)的都是只讀的。計(jì)算Point之間的距離
extension Point {func distance(to: Point) -> Double {let distX = self.x - to.xlet distY = self.y - to.yreturn sqrt(distX * distX + distY * distY)} } pointC.distance(to: Point(0, y: 0))當(dāng)我們定義一個(gè)移動(dòng)X軸坐標(biāo)點(diǎn)的方法時(shí),會(huì)導(dǎo)致編譯錯(cuò)誤:
extension Point {func move(to: Point) {self = to } }這里提示self is immutable , 必須使用mutating修飾這個(gè)方法, Swift編譯器就會(huì)在所有的mutating方法第一個(gè)參數(shù)的位置,自動(dòng)添加一個(gè) inout Self參數(shù)。
extension Point {/* self: inout Self */mutating func move(to: Point) {self = to} }以上,是關(guān)于Struct類型的基本內(nèi)容。
- init方法的合成規(guī)則
 - 值語義在struct上的表現(xiàn)
 
Enum
在Swift中,對(duì)enum做了諸多改進(jìn)和增強(qiáng),它可以有自己的屬性,方法,還可以遵從protocol。
定義enum
定義了一個(gè)colorName枚舉
enum ColorName {case blackcase silvercase graycase whitecase red//.... and so on .... } // 也可以寫在同一行上,用逗號(hào)隔開: enum Month {case january, februray, march,april, may, june, july,august, september, october,november, december }使用
let black = ColorName.black let jan = Month.january注意:
與C和Objective-C不同,Swift的枚舉成員在被創(chuàng)建時(shí)不會(huì)被賦予一個(gè)默認(rèn)的整數(shù)值。上面定義的枚舉成員是完備的值,這些值的類型就是定義好的枚舉ColorName或Month。
理解Enum的“Value”
case 本身就是值
func myColor(color: ColorName) -> String {switch color {case .black:return "black"case .red:return "red"default :return "other"} }注意
- color的類型可以通過type inference推導(dǎo)出是ColorName。因此,可以省略enum的名字。
 
- 當(dāng)Switch...case...將color的所有的值都列舉出來時(shí),可以省略default。
 
綁定值(raw values)
在Swift中,enum默認(rèn)不會(huì)為case綁定一個(gè)整數(shù)值。但是我們可以手動(dòng)的綁定值,這個(gè)“綁定”來的值,叫做raw values。
enum Direction : Int {case eastcase southcase westcase north }現(xiàn)在定義Direction,Swift就會(huì)依次把case綁定上值。
let east = Direction.east.rawValue // 0關(guān)聯(lián)值(Associated value)
在Swift中, 我們可以給每一個(gè)case綁定不同類型的值,我們管這種值叫做Associated value。
定義了一個(gè)表示CSSColor的enum:
enum CSSColor {case named(ColorName)case rgb(UInt8, UInt8, UInt8) }使用:
var color1 = CSSColor.named(.black) var color2 = CSSColor.rgb(0xAA, 0xAA, 0xAA) switch color2 { case let .named(color):print("\(color)") case .rgb(let r, let g, let b):print("\(r), \(g), \(b)") }注意:
提取”關(guān)聯(lián)值“的內(nèi)容時(shí),可以把let和var寫在case前面或者后面。例如:named和rgb。
協(xié)議和方法(Protocol and Method)
在Swift中,enum和其他的命名類型一樣,也可以采用protocol。
例如: 給CSSColor添加一個(gè)文本表示。
extension CSSColor: CustomStringConvertible {var description: String {switch self {case .named(let colorname):return colorname.rawValuecase .rgb(let red, let green, let blue):return String(format: "#%02X%02X%02X", red, green, blue)}} }結(jié)果:
let color3 = CSSColor.named(.red) let color4 = CSSColor.rgb(0xBB, 0xBB, 0xBB) print("color3=\(color3), color4=\(color4)") //color3=red, color4=#BBBBBB什么是Copy on write (COW) ?
COW是一種常見的計(jì)算機(jī)技術(shù),有助于在復(fù)制結(jié)構(gòu)時(shí)提高性能。例如:一個(gè)數(shù)組中有1000個(gè)元素,如果你復(fù)制數(shù)組到另一個(gè)變量,Swift將復(fù)制全部的元素,即使最終兩個(gè)數(shù)組的內(nèi)容相同。
這個(gè)問題可以使用COW解決:當(dāng)將兩個(gè)變量指向同一數(shù)組時(shí),他們指向相同的底層數(shù)據(jù)。兩個(gè)變量指向相同的數(shù)據(jù)可能看起來矛盾。解決方法:當(dāng)修改第二個(gè)變量的時(shí)候,Swift才會(huì)去復(fù)制一個(gè)副本,第一個(gè)不會(huì)改變。
通過延遲復(fù)制操作,直到實(shí)際使用到的時(shí)候 才去復(fù)制,以此確保沒有浪費(fèi)的工作。
注意:COW是特別添加到Swift數(shù)組和字典的功能,自定義的數(shù)據(jù)類型不會(huì)自動(dòng)實(shí)現(xiàn)。
值類型和引用類型(Value vs. Reference Type)
Class和Struct有很多相似的地方,他們都可以用來自定義類型、都可以有屬性、都可以有方法。作為Swift中的引用類型,class表達(dá)的是一個(gè)具有明生命周期的對(duì)象,我們關(guān)心的是類的生命周期。而值類型,我關(guān)注的是值本身。
差異對(duì)比
Swift中class不會(huì)自動(dòng)生成init方法。如果不定義編譯器報(bào)錯(cuò)。
引用類型關(guān)注的是對(duì)象本身
Circle (定義為Class)
Circle(定義為Struct)
var a = Circle() a.radius = 80 var b = a a.radius = 1000 b.radius // 80使用值類型創(chuàng)建新對(duì)象時(shí),將復(fù)制;使用引用類型時(shí),新變量引用同一個(gè)對(duì)象。這是兩者的關(guān)鍵區(qū)別。
我們之前提到過,給struct添加的方法,默認(rèn)的都是只讀的。如果要修改必須用mutating來修飾。class中則不同,我們可以直接給 self賦值。
Class
理解class類型的各種init方法
由于class之間可以存在繼承關(guān)系,因此它的初始化過程要比struct復(fù)雜,為了保證一個(gè)class中的所有屬性都被初始化,Swift中引入一系列特定規(guī)則。
class Point2D {var x : Doublevar y : Double }這項(xiàng)寫是不行了,因?yàn)闆]有定義初始化方法。
指定構(gòu)造器(Designated init)
上面的Point2D有一個(gè)默認(rèn)的初始化方法,有兩種辦法:第一種給每一個(gè)屬性都添加默認(rèn)值。
class Point2D {var x : Double = 0var y : Double = 0 } let origin = Point2D()這種方法只能創(chuàng)建一個(gè)固定的class。另外一種,添加一個(gè)memberwise init 方法
class Point2D {var x : Double = 0var y : Double = 0init(x: Double, y: Double) {self.x = xself.y = y} }添加個(gè)一個(gè)memberwise init方法,我們可以使用
let point = Point2D(x: 1, y: 1)但是,如果你現(xiàn)在使用
let point = Point2D() // Error結(jié)果會(huì)導(dǎo)致編譯錯(cuò)誤。 因?yàn)?#xff0c;我們接手了init的定義后,編譯就不會(huì)插手init工作。所以,在定義init方法時(shí)添加默認(rèn)參數(shù), 我們稱這種初始化為 designated init。
class Point2D {var x : Double = 0var y : Double = 0init(x: Double = 0, y: Double = 0) {self.x = xself.y = y} }便利構(gòu)造器 (convenience init)
class Point2D {var x : Double = 0var y : Double = 0init(x: Double = 0, y: Double = 0) {self.x = xself.y = y}convenience init(at: (Double, Double) ) {self.init(x: at.0, y: at.1)} }- 使用convenience關(guān)鍵字修改;
 - 必須調(diào)用designated init完成對(duì)象的初始化;如果直接調(diào)用self.x或self.y,會(huì)導(dǎo)致編譯錯(cuò)誤。
 
可失敗構(gòu)造器 (Failable init )
class Point2D {// .... convenience init?(at: (String, String)) {guard let x = Double(at.0), let y = Double(at.1) else {return nil}self.init(at:(x, y))} }由于String tuple版的init可能失敗,所以需要用init?形式定義。在實(shí)現(xiàn)里面,如果String無法轉(zhuǎn)換為成Double, 則返回nil。
注意:
嚴(yán)格來說,構(gòu)造器都不支持返回值。因?yàn)闃?gòu)造器本身的作用,只是為了確保對(duì)象能被正確構(gòu)造。因此,return nil表示構(gòu)造失敗,而不能return表示成功。
類的繼承和構(gòu)造過程
當(dāng)類之間存在繼承關(guān)系的時(shí)候,為了保證派生類和基類的屬性都被初始化,Swift采用以下三條規(guī)則限制構(gòu)造器之間的代理調(diào)用:
- 指定構(gòu)造器必須調(diào)用其直接父類的指定構(gòu)造器
 - 便利構(gòu)造器必須調(diào)用同類中定義的其它構(gòu)造器
 - 便利構(gòu)造器必須最終導(dǎo)致一個(gè)指定構(gòu)造器被調(diào)用
 
簡(jiǎn)單說:
- 指定構(gòu)造器必須總是向上代理
 - 便利構(gòu)造器必須總是橫向代理
 
init的繼承
class Point3D: Point2D {var z: Double = 0 } let origin3D = Point3D() let point31 = Point3D(x: 1, y: 1) let point33 = Point3D(at: (2, 3)) // 繼承基類 convenience init- 如果派生類沒有定義任何designated initializer,那么它將自動(dòng)繼承所有基類的designated initializer。
 - 如果一個(gè)派生類定義了所有基類的designated init,那么它將自動(dòng)繼承基類所有的convenience init。
 
重載init方法
class Point3D: Point2D {var z: Doubleinit(x: Double = 0, y: Double = 0, z: Double = 0) {self.z = zsuper.init(x: x, y: y)} }在派生類自定義designated init, 表示明確控制派生類的初始化構(gòu)造過程, Swift 就不會(huì)干涉構(gòu)造過程。那么,之前創(chuàng)建Point3D就會(huì)出現(xiàn)錯(cuò)誤。
let point33 = Point3D(at: (2, 3)) // Error如果想讓Point3D從Point2D繼承所有的convenience init,只有在派生類中實(shí)現(xiàn)所有的designated init方法。
class Point3D: Point2D {var z: Doubleinit(x: Double = 0, y: Double = 0, z: Double = 0) {self.z = zsuper.init(x: x, y: y)}override init(x: Double, y: Double) {// 注意先后順序self.z = 0super.init(x: x, y: y)} }此時(shí),就可以正常工作了。只要派生類擁有基類所有的designated init方法,他就會(huì)自動(dòng)獲得所有基類的convenience init方法。另外,重載基類convenience init方法,是不需要override關(guān)鍵字修飾的。
兩段式構(gòu)造過程
Swift為了保證在一個(gè)繼承關(guān)系中,派生類和基類的屬性都可以正確初始化而約定的初始化機(jī)制。簡(jiǎn)單來說,這個(gè)機(jī)制把派生類的初始化過程分成了兩個(gè)階段。
- 階段一: 從派生類到基類,自下而上讓類的每個(gè)屬性有初始值
 - 階段二:所有屬性都有初始值之后,從基類到派生類,自上而下對(duì)類的每個(gè)屬性進(jìn)行進(jìn)一步加工。
 
兩段式構(gòu)造過程讓構(gòu)造過程更安全,同時(shí)整個(gè)類層級(jí)結(jié)構(gòu)中給予了每個(gè)類完全的靈活性。兩段式構(gòu)造過程可以防止屬性值在初始化之前被訪問,也可以防止屬性被另外一個(gè)構(gòu)造器意外賦予不同的值。
參考
[The swift Programming Language]()
Swift Standard Library
如何學(xué)習(xí)Swift編程語言-泊學(xué)
Getting to Know Enums, Structs and Classes in Swift - raywenderlich
轉(zhuǎn)載于:https://blog.51cto.com/13533483/2050344
總結(jié)
以上是生活随笔為你收集整理的关于Swift中Struct,Class和Enum的哪些事儿的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 格林童话故事100篇精选
 - 下一篇: 机器学习基础:分类vs回归