【swift開發】關於Swift中Struct,Class和Enum的那些事兒

2022-01-09 安卓巴士Android開發者門戶

點上方安卓巴士Android開發者門戶,獲取更多乾貨及教程

Swift type System

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

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

| Struct Class and Enum 比較 |

Swift中提供了多種可以結構化存儲數據的方式,它們是: struct、enum和
class。Swift標準庫中的絕大多數類型都是struct,甚至Foundation中的一些類也提供了它們在Swift中的struct版本,而class和enum只佔很少一部分。

Class,Struct and Enum對比表共同點:

都可以當作protocol

都可以使用extension,擴充method

都可以使用泛型

如何抉擇?|

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

一類必須有明確生命周期的,它們必須被明確的初始化、使用、最後明確的被釋放。例如:文件句柄、資料庫連接、線程同步鎖等等。這些類型的初始化和釋放都不是拷貝內存這麼簡單,通常,這類內容,我們會選擇使用class來實現。

另一類,則是沒有那麼明顯的生命周期。 例如:整數、字符串、URL等等。這些對象一旦被創建之後,就很少被修改,我們只是需要使用這些對象的值,用完之後,我們也無需為這些對象的銷毀做更多額外的工作,只是把它們佔用的內存回收就好了。這類內容,通常我們會選擇使用struct或enum來實現。

| Struct |Struct的定義和初始化

定義結構體
下面定義了一個二維空間坐標的類型:

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

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

初始化

結構體類型的逐一初始化
所有的結構體都有一個自動生成的成員逐一構造器

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將不會再自動創建逐一初始化方法。

Struct 值類型本質

var pointB = Point(200, y: 100)
var pointC = Point(100, y: 200) {
didSet {
print("\(pointC)")
}
}
pointC = pointB

pointC.x = 200

通過didSet觀察pointC的變化。當修改pointC變量值時,控制臺輸出Point(x: 200.0, y: 100.0), 但是,修改pointC的修改某個屬性,也會觸發didSet。

這就是值語義的本質:即使字面上修改了pointC變量的某個屬性,但實際執行的邏輯是重新給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方法第一個參數的位置,自動添加一個 inout Self參數。

extension Point {

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

以上,是關於Struct類型的基本內容。

init方法的合成規則

值語義在struct上的表現

| Enum |

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

定義enum

定義了一個colorName枚舉

enum ColorName {
case black
case silver
case gray
case white
case red

}

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的枚舉成員在被創建時不會被賦予一個默認的整數值。上面定義的枚舉成員是完備的值,這些值的類型就是定義好的枚舉ColorName或Month。

理解Enum的「Value」case 本身就是值

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

注意

綁定值(raw values)

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

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

現在定義Direction,Swift就會依次把case綁定上值。

let east = Direction.east.rawValue

關聯值(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)")
}

注意:
提取」關聯值「的內容時,可以把let和var寫在case前面或者後面。例如:named和rgb。

協議和方法(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)
}
}
}

結果:

let color3 = CSSColor.named(.red)
let color4 = CSSColor.rgb(0xBB, 0xBB, 0xBB)
print("color3=\(color3), color4=\(color4)")
//color3=red, color4=

|  什麼是Copy on write (COW) ?|

COW是一種常見的計算機技術,有助於在複製結構時提高性能。例如:一個數組中有1000個元素,如果你複製數組到另一個變量,Swift將複製全部的元素,即使最終兩個數組的內容相同。

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

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

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

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

差異對比

引用類型必須明確指定init方法
Swift中class不會自動生成init方法。如果不定義編譯器報錯。

引用類型關注的是對象本身
Circle (定義為Class)

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

Circle(定義為Struct)

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

使用值類型創建新對象時,將複製;使用引用類型時,新變量引用同一個對象。這是兩者的關鍵區別。

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

| Class |理解class類型的各種init方法

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

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

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

指定構造器(Designated init)

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

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

這種方法只能創建一個固定的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)

但是,如果你現在使用

let point = Point2D()

結果會導致編譯錯誤。因為,我們接手了init的定義後,編譯就不會插手init工作。所以,在定義init方法時添加默認參數, 我們稱這種初始化為 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
}
}

|  便利構造器 (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)
}
}

可失敗構造器 (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?形式定義。在實現裡面,如果String無法轉換為成Double, 則返回nil。

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

類的繼承和構造過程 |

當類之間存在繼承關係的時候,為了保證派生類和基類的屬性都被初始化,Swift採用以下三條規則限制構造器之間的代理調用:

指定構造器必須調用其直接父類的指定構造器

便利構造器必須調用同類中定義的其它構造器

便利構造器必須最終導致一個指定構造器被調用

簡單說:

指定構造器必須總是向上代理

便利構造器必須總是橫向代理

init的繼承 |

class Point3D: Point2D {
var z: Double = 0
}
let origin3D = Point3D()
let point31 = Point3D(x: 1, y: 1)
let point33 = Point3D(at: (2, 3))

如果派生類沒有定義任何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, 表示明確控制派生類的初始化構造過程, Swift 就不會幹涉構造過程。那麼,之前創建Point3D就會出現錯誤。

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

如果想讓Point3D從Point2D繼承所有的convenience init,只有在派生類中實現所有的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關鍵字修飾的。

| 兩段式構造過程 |

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

階段一:從派生類到基類,自下而上讓類的每個屬性有初始值

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

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

相關推薦 |


推薦相關課程:

>swift實戰項目開發今日頭條

感謝您一直以來的關注,歡迎在文章下方留言評論

巴士官網:www.apkbus.com

相關實戰開發課程:qiantao.ke.qq.com

滑到這裡啦,點個在看再溜

相關焦點

  • iOS面試題-Swift篇
    在 Swift 中,class 是引用類型(指針類型), struct 是值類型值類型在傳遞和賦值時將進行複製; 賦值給var、let或者給函數傳參,是直接將所有內容拷貝一份, 類似於對文件進行copy、paste操作,產生了全新的文件副本。
  • 從Mach-O角度談談Swift和OC的存儲差異
    歸根到底還是由於Mach-O文件存儲了類和函數的信息。在Mach-O中,所有的類都存儲到__objc_classlist這個section中。通過 __objc_classlist中的地址,我們能找到每個類的詳細信息。本文以arm64架構為例,在找到0x11820文件偏移後,我們很容易通過結構體結構套取到類的信息。
  • Swift 中的類與結構體
    本文結合源碼探究類和結構體的本質。類和結構體的異同Swift中,類和結構體有許多相似之處,但也有不同。我們都知道,內存分配可以分為堆區(Heap)和棧區(Stack)。由於棧區內存是連續的,內存的分配和銷毀是通過入棧和出棧操作進行的,速度要高於堆區。堆區存儲高級數據類型,在數據初始化時,查找沒有使用的內存,銷毀時再從內存中清除,所以堆區的數據存儲不一定是連續的。
  • Swift 語言學習及速查手冊
    當然,這也和新語言的不斷演變進化有關係。我利用周末兩天時間,把《Swift Programming Language》中文版整整的細看了一遍,然後為了總結提取 Swift 的主要語言特性,又把這本書快速過了第二遍。
  • 從Java到Swift
    Swift程序的默認入口是main.swift文件,在iOS應用中,則通常標記了@UIApplicationMain的AppDelegate.swift文件。可以類比到Android中,在AndroidManifest.xml中定義的Application。2.Swift不需要定義行結束符,這個是像腳本語言一樣。
  • 來一次有側重點的區分Swift與Objective-C
    相比於OC中的nil更加安全和簡明- swift中的泛型類型更加方便和通用,而非OC中只能為集合類型添加泛型- swift中各種方便快捷的高階函數(函數式編程) (Swift的標準數組支持三個高階函數:map,filter和reduce,以及map的擴展flatMap)- swift新增了兩種權限,細化權限。
  • Swift 反射 API 及用法
    可能你已經在很多博客文章或者類似Tuples、Midi Packets 和 Core Data 的項目中見過它。也許你剛好對在項目中使用反射機制感興趣,或者你想更好的了解反射可以應用的領域,那這篇文章就正是你需要的。文章的內容是基於我在德國法蘭克福 Macoun會議上的一次演講,它對 Swift 的反射 API 做了一個概述。
  • Swift中編寫單例的正確方式
    單例規則關於單例,有三個重要的準則需要牢記:1. 單例必須是唯一的(要不怎麼叫單例?) 在程序生命周期中只能存在一個這樣的實例。單例的存在使我們可以全局訪問狀態。例如:NSNotificationCenter, UIApplication和NSUserDefaults。2.
  • Swift學習: 從Objective-C到Swift
    1.屬性(property)和實例變量(instance variable)Objective-C property in Swift world在Cocoa世界開發的過程中,我們最常打交道的是property.
  • 用 Swift 來寫命令行程序
    我想你會發現 Swift 為兩種類型的工程師都提供了很多有用的特性,並且會成為 Linux 開發體系中一股很受歡迎的新力量。// Import statementsimport Foundationimport Glibc// Enumerationsenum CommandType {case Nonecase Translatecase SetFromcase SetTocase Quit}// Structsstruct Command {  var type:CommandType  var data
  • Apple 在 iOS 13.1 中使用 Swift 開發的應用程式
    從那時起,iOS 10.1,iOS 11.1 和 iOS 12.0 中使用 Swift 的應用程式數量逐年增加。現在有了 iOS 13.1,讓我們來看看一下今年有多少應用程式在使用 Swift。檢測方法在先前的文章中已經詳細介紹了本文使用的檢測方法和工具。
  • 《 Swift 程式語言》中文版教程開源
    https://github.com/SwiftGGTeam/the-swift-programming-language-in-chinese項目使用GitBook製作的,可以在線閱讀,原文和翻譯版網址如下。
  • 淺談 swiftinterface 文件
    打開這個選項等價於增加兩個 swiftc 的編譯參數:-enable-library-evolution 和 -emit-module-interface-path。為了更好的解釋模塊穩定性的原理,以及我們是如何在性能和靈活性之間進行取捨的,這裡再舉一個簡單的例子,修改代碼如下:public struct People { internal var name: String @usableFromInline
  • Swift代碼優化指南 | 如何最大化實現性能提升?
    基於快手主站擁抱 Swift 的美好願景,主站的業務需求和重構需求都會逐步使用 Swift 進行開發。本篇文章總結了一些 Swift 優化方案,從內存佔用、編譯期、運行時速度提升等角度,結合底層原理進行分析總結。
  • Swift vs. Kotlin 漫談系列之接口
    我們實現協議的類型除了 class 外,還可以是 struct 或 enum。Kotlin:Kotlin 沒有結構體的概念, enum 也可以實現接口。來說說你們的接口中可以聲明哪些東西吧?我們甚至可以在擴展中添加協議裡沒有定義過的方法和屬性。
  • SwiftUI 的 DSL 語法分析
    struct RoomDetail : View {    @State var zoomed = false}但講述前,先岔開一下,說一下 Attribute。在中文中,Property 和 Attribute 都被翻譯成屬性了。但兩者在 Swift 中是不同的概念。
  • RxSwift 實戰教程-核心用法
    一般做iOS開發的要使用到RxSwift都要用到RxCocoa的,這兩個是相輔相成的。我們在protocol.swift文件中使用一個枚舉表示我們處理結果:所以我們在protocol.swift文件中添加下面內容// 表示我們的一些請求的結果enum Result {    case ok(message: String)    case empty    case failed(message: String)
  • 淺談 Swift Dictionary
    背景在最近排查問題的過程中,發現經常需要和字典的彙編碼打交道。
  • 如何才能編寫高性能的 Swift 代碼
    然而文檔中還有一些技巧是不符合規矩的,扭曲的,僅僅解決一些比編譯器或語言的特殊的臨時性需求。文檔中的很多建議來自於多方面的權衡,例如:運行時、字 節大小、代碼可讀性等等。啟用優化第一個應該做的事情就是啟用優化。Swift 提供了三種不同的優化級別:-Onone: 這意味著正常的開發。它執行最小優化和保存所有調試信息。
  • swift語言是什麼?蘋果最新編程swift語言資料
    swift語言是什麼?蘋果最新編程swift語言資料    Swift是Apple在WWDC2014所發布的一門程式語言,用來撰寫OS X和iOS應用程式。在設計Swift時.就有意和Objective-C共存,Objective-C是Apple作業系統在導入Swift前使用的程式語言。