您好,登錄后才能下訂單哦!
Swift type System
Swift是強類型的,盡管只有六種類型。
可能會有疑問,那些基本類型:Bool,Int,UInt, Float, Double, Character, String, Array, Set, Dictionary, Optional。實際上他們都是通過命名類型創建的。
Swift中提供了多種可以結構化存儲數據的方式,它們是: struct
、enum
和class
。Swift標準庫中的絕大多數類型都是struct
,甚至Foundation中的一些類也提供了它們在Swift中的struct
版本,而class
和enum
只占很少一部分。
Class,Struct and Enum對比表
copy by | inheritance | static variable | instance variable | static method | instance method | |
---|---|---|---|---|---|---|
Class | Reference | ? | ? | ? | ? | ? |
Struct | Value | ? | ? | ? | ? | ? |
Enum | Value | ? | ? | ? | ? | ? |
共同點:
protocol
extension
,擴充method
通常,在平時的編程中,按照對象的生命周期形態,可以把使用的類型分成兩大類:
class
來實現。struct
或enum
來實現。 定義結構體
下面定義了一個二維空間坐標的類型:
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將不會再自動創建逐一初始化方法。
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
的修改某個屬性,也會觸發didSet
。
這就是值語義的本質:即使字面上修改了pointC
變量的某個屬性,但實際執行的邏輯是重新給pointC
賦值一個新的Point
對象。
給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 {
/* self: inout Self */
mutating func move(to: Point) {
self = to
}
}
以上,是關于Struct類型的基本內容。
init
方法的合成規則struct
上的表現在Swift中,對enum
做了諸多改進和增強,它可以有自己的屬性,方法,還可以遵從protocol
。
定義了一個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的枚舉成員在被創建時不會被賦予一個默認的整數值。上面定義的枚舉成員是完備的值,這些值的類型就是定義好的枚舉ColorName
或Month
。
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
。
在Swift中,enum
默認不會為case
綁定一個整數值。但是我們可以手動的綁定值,這個“綁定”來的值,叫做raw values。
enum Direction : Int {
case east
case south
case west
case north
}
現在定義Direction,Swift就會依次把case
綁定上值。
let east = Direction.east.rawValue // 0
在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
。
在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=#BBBBBB
COW是一種常見的計算機技術,有助于在復制結構時提高性能。例如:一個數組中有1000個元素,如果你復制數組到另一個變量,Swift將復制全部的元素,即使最終兩個數組的內容相同。
這個問題可以使用COW解決:當將兩個變量指向同一數組時,他們指向相同的底層數據。兩個變量指向相同的數據可能看起來矛盾。解決方法:當修改第二個變量的時候,Swift才會去復制一個副本,第一個不會改變。
通過延遲復制操作,直到實際使用到的時候 才去復制,以此確保沒有浪費的工作。
注意:COW是特別添加到Swift數組和字典的功能,自定義的數據類型不會自動實現。
Class和Struct有很多相似的地方,他們都可以用來自定義類型、都可以有屬性、都可以有方法。作為Swift中的引用類型,class
表達的是一個具有明生命周期的對象,我們關心的是類的生命周期。而值類型,我關注的是值本身。
class
不會自動生成init
方法。如果不定義編譯器報錯。引用類型關注的是對象本身
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
使用值類型創建新對象時,將復制;使用引用類型時,新變量引用同一個對象。這是兩者的關鍵區別。
struct
添加的方法,默認的都是只讀的。如果要修改必須用mutating
來修飾。class
中則不同,我們可以直接給 self
賦值。由于class
之間可以存在繼承關系,因此它的初始化過程要比struct
復雜,為了保證一個class
中的所有屬性都被初始化,Swift中引入一系列特定規則。
class Point2D {
var x : Double
var y : Double
}
這項寫是不行了,因為沒有定義初始化方法。
上面的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() // Error
結果會導致編譯錯誤。 因為,我們接手了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
}
}
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
關鍵字修改;designated init
完成對象的初始化;如果直接調用self.x或self.y,會導致編譯錯誤。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采用以下三條規則限制構造器之間的代理調用:
簡單說:
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
。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)) // Error
如果想讓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為了保證在一個繼承關系中,派生類和基類的屬性都可以正確初始化而約定的初始化機制。簡單來說,這個機制把派生類的初始化過程分成了兩個階段。
兩段式構造過程讓構造過程更安全,同時整個類層級結構中給予了每個類完全的靈活性。兩段式構造過程可以防止屬性值在初始化之前被訪問,也可以防止屬性被另外一個構造器意外賦予不同的值。
[The swift Programming Language]()
Swift Standard Library
如何學習Swift編程語言-泊學
Getting to Know Enums, Structs and Classes in Swift - raywenderlich
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。