溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

關于Swift中Struct,Class和Enum的哪些事兒

發(fā)布時間:2020-07-25 07:03:04 來源:網(wǎng)絡 閱讀:20275 作者:Owenli_千 欄目:移動開發(fā)

前言

Swift type System

關于Swift中Struct,Class和Enum的哪些事兒

Swift是強類型的,盡管只有六種類型。

  • 命名類型: protocol, class , struct , enum
  • 復合類型:tuple, function

可能會有疑問,那些基本類型:Bool,Int,UInt, Float, Double, Character, String, Array, Set, Dictionary, Optional。實際上他們都是通過命名類型創(chuàng)建的。

Struct Class and Enum 比較

Swift中提供了多種可以結(jié)構(gòu)化存儲數(shù)據(jù)的方式,它們是: structenum
class。Swift標準庫中的絕大多數(shù)類型都是struct,甚至Foundation中的一些類也提供了它們在Swift中的struct版本,而classenum只占很少一部分。

Class,Struct and Enum對比表

copy by inheritance static variable instance variable static method instance method
Class Reference ? ? ? ? ?
Struct Value ? ? ? ? ?
Enum Value ? ? ? ? ?

共同點:

  1. 都可以當作protocol
  2. 都可以使用extension,擴充method
  3. 都可以使用泛型

如何抉擇?

通常,在平時的編程中,按照對象的生命周期形態(tài),可以把使用的類型分成兩大類:

  • 一類必須有明確生命周期的,它們必須被明確的初始化、使用、最后明確的被釋放。例如:文件句柄、數(shù)據(jù)庫連接、線程同步鎖等等。這些類型的初始化和釋放都不是拷貝內(nèi)存這么簡單,通常,這類內(nèi)容,我們會選擇使用class來實現(xiàn)。
  • 另一類,則是沒有那么明顯的生命周期。 例如:整數(shù)、字符串、URL等等。這些對象一旦被創(chuàng)建之后,就很少被修改,我們只是需要使用這些對象的值,用完之后,我們也無需為這些對象的銷毀做更多額外的工作,只是把它們占用的內(nèi)存回收就好了。這類內(nèi)容,通常我們會選擇使用structenum來實現(xiàn)。

Struct

Struct的定義和初始化

定義結(jié)構(gòu)體
下面定義了一個二維空間坐標的類型:

struct Point {
    var x: Double
    var y: Double
}

這個結(jié)構(gòu)體包含兩個名x和y的存儲屬性。存儲屬性是被綁定和存儲在結(jié)構(gòu)體中的常量或變量。

初始化

  • 結(jié)構(gòu)體類型的逐一初始化
    所有的結(jié)構(gòu)體都有一個自動生成的成員逐一構(gòu)造器
    var pointA = Point(x: 10, y: 20)
  • 默認初始化
    我們也可以在定義的時候直接給屬性初始化
    struct Point {
    var x = 0.0
    var y = 0.0
    }
    var pointB = Point()

    使用這種方法,必須給每一個屬性指定默認值。因為Swift中要求init方法必須初始化自定義類型每個屬性。如果無法做到,我們可以自定義逐一初始化方法。

    struct Point {
    var x : Double
    var y : Double
    init(_ x : Double = 0.0, y : Double = 0.0) {
        self.x = x
        self.y = y
    }
    }

    當我們自定義init方法之后,Swift將不會再自動創(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的變化。當修改pointC變量值時,控制臺輸出Point(x: 200.0, y: 100.0), 但是,修改pointC的修改某個屬性,也會觸發(fā)didSet。

這就是值語義的本質(zhì):即使字面上修改了pointC變量的某個屬性,但實際執(zhí)行的邏輯是重新給pointC賦值一個新的Point對象。

為Struct添加方法

struct添加的方法,默認的都是只讀的。計算Point之間的距離

extension Point {
    func distance(to: Point) -> Double {
        let distX = self.x - to.x
        let distY = self.y - to.y
        return sqrt(distX * distX + distY * distY)
    }
}
pointC.distance(to: Point(0, y: 0))

當我們定義一個移動X軸坐標點的方法時,會導致編譯錯誤:

extension Point {
    func move(to: Point) {
        self = to 
    }
}

這里提示self is immutable , 必須使用mutating修飾這個方法, Swift編譯器就會在所有的mutating方法第一個參數(shù)的位置,自動添加一個 inout Self參數(shù)。

extension Point {
    /* self: inout Self */
    mutating func move(to: Point) {
        self = to
    }
}

以上,是關于Struct類型的基本內(nèi)容。

  • init方法的合成規(guī)則
  • 值語義在struct上的表現(xiàn)

Enum

在Swift中,對enum做了諸多改進和增強,它可以有自己的屬性,方法,還可以遵從protocol

定義enum

定義了一個colorName枚舉

enum ColorName {
    case black
    case silver
    case gray
    case white
    case red
    //.... and so on ....
}
// 也可以寫在同一行上,用逗號隔開:
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ù)值。上面定義的枚舉成員是完備的值,這些值的類型就是定義好的枚舉ColorNameMonth。

理解Enum的“Value”

case 本身就是值

func myColor(color: ColorName) -> String {
    switch color {
    case .black:
        return "black"
    case .red:
        return "red"
    default :
        return "other"
    }
}

注意

  • color的類型可以通過type inference推導出是ColorName。因此,可以省略enum的名字。
  • 當Switch...case...將color的所有的值都列舉出來時,可以省略default。

綁定值(raw values)

在Swift中,enum默認不會為case綁定一個整數(shù)值。但是我們可以手動的綁定值,這個“綁定”來的值,叫做raw values。

enum Direction : Int {
    case east
    case south
    case west
    case north
}

現(xiàn)在定義Direction,Swift就會依次把case綁定上值。

let east = Direction.east.rawValue // 0

關聯(lián)值(Associated value)

在Swift中, 我們可以給每一個case綁定不同類型的值,我們管這種值叫做Associated value。

定義了一個表示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)")
}

注意:
提取”關聯(lián)值“的內(nèi)容時,可以把letvar寫在case前面或者后面。例如:namedrgb。

協(xié)議和方法(Protocol and Method)

在Swift中,enum和其他的命名類型一樣,也可以采用protocol。

例如: 給CSSColor添加一個文本表示。

extension CSSColor: CustomStringConvertible {
    var description: String {
        switch self {
        case .named(let colorname):
            return colorname.rawValue
        case .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是一種常見的計算機技術,有助于在復制結(jié)構(gòu)時提高性能。例如:一個數(shù)組中有1000個元素,如果你復制數(shù)組到另一個變量,Swift將復制全部的元素,即使最終兩個數(shù)組的內(nèi)容相同。

這個問題可以使用COW解決:當將兩個變量指向同一數(shù)組時,他們指向相同的底層數(shù)據(jù)。兩個變量指向相同的數(shù)據(jù)可能看起來矛盾。解決方法:當修改第二個變量的時候,Swift才會去復制一個副本,第一個不會改變。
通過延遲復制操作,直到實際使用到的時候 才去復制,以此確保沒有浪費的工作。

注意:COW是特別添加到Swift數(shù)組和字典的功能,自定義的數(shù)據(jù)類型不會自動實現(xiàn)。

值類型和引用類型(Value vs. Reference Type)

Class和Struct有很多相似的地方,他們都可以用來自定義類型、都可以有屬性、都可以有方法。作為Swift中的引用類型,class表達的是一個具有明生命周期的對象,我們關心的是類的生命周期。而值類型,我關注的是值本身。

差異對比

  1. 引用類型必須明確指定init方法
    Swift中class不會自動生成init方法。如果不定義編譯器報錯。
  2. 引用類型關注的是對象本身
    Circle (定義為Class)

    var a = Circle()
    a.radius = 80
    var b = a
    a.radius = 1000
    b.radius // 1000

    Circle(定義為Struct)

    var a = Circle()
    a.radius = 80
    var b = a
    a.radius = 1000
    b.radius // 80

    使用值類型創(chuàng)建新對象時,將復制;使用引用類型時,新變量引用同一個對象。這是兩者的關鍵區(qū)別。

  3. 引用類型的默認值是可以修改的
    我們之前提到過,給struct添加的方法,默認的都是只讀的。如果要修改必須用mutating來修飾。class中則不同,我們可以直接給 self賦值。

Class

理解class類型的各種init方法

由于class之間可以存在繼承關系,因此它的初始化過程要比struct復雜,為了保證一個class中的所有屬性都被初始化,Swift中引入一系列特定規(guī)則。

class Point2D {
    var x : Double
    var y : Double
}

這項寫是不行了,因為沒有定義初始化方法。

指定構(gòu)造器(Designated init)

上面的Point2D有一個默認的初始化方法,有兩種辦法:第一種給每一個屬性都添加默認值。

class Point2D {
    var x : Double = 0
    var y : Double = 0
}
let origin = Point2D()

這種方法只能創(chuàng)建一個固定的class。另外一種,添加一個memberwise init 方法

class Point2D {
    var x : Double = 0
    var y : Double = 0
    init(x: Double, y: Double) {
        self.x = x
        self.y = y
    }
}

添加個一個memberwise init方法,我們可以使用

let point = Point2D(x: 1, y: 1)

但是,如果你現(xiàn)在使用

let point = Point2D()  // Error 

結(jié)果會導致編譯錯誤。 因為,我們接手了init的定義后,編譯就不會插手init工作。所以,在定義init方法時添加默認參數(shù), 我們稱這種初始化為 designated init

class Point2D {
    var x : Double = 0
    var y : Double = 0
    init(x: Double = 0, y: Double = 0) {
        self.x = x
        self.y = y
    }
}

便利構(gòu)造器 (convenience init)

class Point2D {
    var x : Double = 0
    var y : Double = 0
    init(x: Double = 0, y: Double = 0) {
        self.x = x
        self.y = y
    }
    convenience init(at: (Double, Double) ) {
        self.init(x: at.0, y: at.1)
    }
}
  • 使用convenience關鍵字修改;
  • 必須調(diào)用designated init完成對象的初始化;如果直接調(diào)用self.x或self.y,會導致編譯錯誤。

可失敗構(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?形式定義。在實現(xiàn)里面,如果String無法轉(zhuǎn)換為成Double, 則返回nil

注意:
嚴格來說,構(gòu)造器都不支持返回值。因為構(gòu)造器本身的作用,只是為了確保對象能被正確構(gòu)造。因此,return nil表示構(gòu)造失敗,而不能return表示成功。

類的繼承和構(gòu)造過程

當類之間存在繼承關系的時候,為了保證派生類和基類的屬性都被初始化,Swift采用以下三條規(guī)則限制構(gòu)造器之間的代理調(diào)用:

  • 指定構(gòu)造器必須調(diào)用其直接父類的指定構(gòu)造器
  • 便利構(gòu)造器必須調(diào)用同類中定義的其它構(gòu)造器
  • 便利構(gòu)造器必須最終導致一個指定構(gòu)造器被調(diào)用

簡單說:

  • 指定構(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,那么它將自動繼承所有基類的designated initializer。
  • 如果一個派生類定義了所有基類的designated init,那么它將自動繼承基類所有的convenience init。

重載init方法

class Point3D: Point2D {
    var z: Double
    init(x: Double = 0, y: Double = 0, z: Double = 0) {
        self.z = z
        super.init(x: x, y: y)
    }
}

在派生類自定義designated init, 表示明確控制派生類的初始化構(gòu)造過程, Swift 就不會干涉構(gòu)造過程。那么,之前創(chuàng)建Point3D就會出現(xiàn)錯誤。

let point33 = Point3D(at: (2, 3))  // Error 

如果想讓Point3DPoint2D繼承所有的convenience init,只有在派生類中實現(xiàn)所有的designated init方法。

class Point3D: Point2D {
    var z: Double
    init(x: Double = 0, y: Double = 0, z: Double = 0) {
        self.z = z
        super.init(x: x, y: y)
    }
    override init(x: Double, y: Double) {
        // 注意先后順序
        self.z = 0
        super.init(x: x, y: y)
    }
}

此時,就可以正常工作了。只要派生類擁有基類所有的designated init方法,他就會自動獲得所有基類的convenience init方法。另外,重載基類convenience init方法,是不需要override關鍵字修飾的。

兩段式構(gòu)造過程

Swift為了保證在一個繼承關系中,派生類和基類的屬性都可以正確初始化而約定的初始化機制。簡單來說,這個機制把派生類的初始化過程分成了兩個階段。

  • 階段一: 從派生類到基類,自下而上讓類的每個屬性有初始值
  • 階段二:所有屬性都有初始值之后,從基類到派生類,自上而下對類的每個屬性進行進一步加工。

兩段式構(gòu)造過程讓構(gòu)造過程更安全,同時整個類層級結(jié)構(gòu)中給予了每個類完全的靈活性。兩段式構(gòu)造過程可以防止屬性值在初始化之前被訪問,也可以防止屬性被另外一個構(gòu)造器意外賦予不同的值。

參考

[The swift Programming Language]()
Swift Standard Library
如何學習Swift編程語言-泊學
Getting to Know Enums, Structs and Classes in Swift - raywenderlich

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI