類與結構是編程人員在代碼中會經常用到的代碼塊。在類與結構中可以像定義常量,變量和函數一樣,定義相關的屬性和方法以此來實現各種功能。
和其它的編程語言不太相同的是,Swift不需要單獨創建接口或者實現文件來使用類或者結構。Swift中的類或者結構可以在單文件中直接定義,一旦定義完成后,就能夠被直接其它代碼使用。
注意:一個類的實例一般被視作一個對象,但是在Swift中,類與結構更像是一個函數方法,在后續的章節中更多地是講述類和結構的功能性。
1、類和結構的異同
類和結構有一些相似的地方,它們都可以:
定義一些可以賦值的屬性;
定義具有功能性的方法
定義下標,使用下標語法
定義初始化方法來設置初始狀態
在原實現方法上的可擴展性
根據協議提供某一特定類別的基本功能
更多內容可以閱讀:屬性,方法,下標,初始化,擴展和協議等章節
類還有一些結構不具備的特性:
類的繼承性
對類實例實時的類型轉換
析構一個類的實例使之釋放空間
引用計數,一個類實例可以有多個引用
更多內容可以閱讀:繼承,類型轉換,初始化自動引用計數
注意:結構每次在代碼中傳遞時都是復制了一整個,所以不要使用引用計數
定義語法
類和結構擁有相似的定義語法,使用class關鍵詞定義一個類,struct關鍵詞定義結構。每個定義都由一對大括號包含:
class SomeClass {
// class definition goes here
}
struct SomeStructure {
// structure definition goes here
}
注意:在定義類和結構時,一般使用UpperCamelCase命名法來定義類和結構的名稱,比如SomeClass和SomeStructure,這樣也符合Swift其它類型的標準。而給屬性和方法命名時,一般時候lowerCamelCase命名法,比如frameRate和incrementCount等。
下面是一個結構和一個類的定義示例:
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = falsevar
frameRate = 0.0
var name: String?
}
上面的例子首先定義了一個叫Resolution的結構,用來描述一個像素顯示的分辨率,它有兩個屬性分別叫width和height。這兩個屬性被默認定義為Int類型,初始化為0.
之后定義了一個叫VideoMode的類,為視頻顯示的顯示方式。這個類有四個屬性,第一個屬性resolution本身又是一個結構,然后是另外兩個屬性。最后一個屬性用到了可選字符串類型String?,表示這個屬性可以存在,或者不存在為nil。
類和結構的實例
上面的兩個定義僅僅是定義了結構Resolution和類VideoMode的整體樣式,它們本身不是一個特定的分辨率或者顯示方式,這時候就需要實例化這個結構和類。
實例化的語法相似:
let someResolution = Resolution()
let someVideoMode = VideoMode()
類和結構都使用實例語法來完成實例化。最簡單的實例語法就是用兩個括號()完成。在這種情況下定義的實例中的屬性都會完成默認初始化。更多內容可以參考初始化一章。
訪問屬性
使用.語法就可以方便地訪問一個實例的屬性。在.語法中,在實例名之后加上(.)再加上屬性名即可,不需要空格:
println("The width of someResolution is /(someResolution.width)")
// prints "The width of someResolution is 0"
在這個例子中,someResolution.width表示someResolution的width屬性,返回了它的初始值0
也可以使用.語法連續地獲取屬性的屬性,比如VideoMode中resolution屬性的width屬性
println("The width of someVideoMode is /(someVideoMode.resolution.width)")
// prints "The width of someVideoMode is 0"
使用這種方法不僅可以訪問,也可以賦值:
someVideoMode.resolution.width = 1280
println("The width of someVideoMode is now /(someVideoMode.resolution.width)")
// prints "The width of someVideoMode is now 1280"
注意:和Objective-C不同,Swift能夠直接設置一個結構屬性的子屬性,就像上面這個例子一樣。
結構類型的成員初始化方法
每個結構都有一個成員初始化方法,可以在初始化的時候通過使用屬性名稱來指定每一個屬性的初始值:
let vga = Resolution(width: 640, height: 480)
但是和結構不同,類實例不能夠使用成員初始化方法,在初始化一章有專門的介紹。
2、結構和枚舉類型是數值類型
數值類型是說當它被賦值給一個常量或者變量,或者作為參數傳遞給函數時,是完整地復制了一個新的數值,而不是僅僅改變了引用對象。
事實上讀到這里你已經在前面幾章見過數值類型了,所有Swift中的基礎類型-整型,浮點型,布爾類型,字符串,數組和字典都是數值類型。它們也都是由結構來實現的。
在Swift中所有的結構和枚舉類型都是數值類型。這意味這你實例化的每個結構和枚舉,其包含的所有屬性,都會在代碼中傳遞的時候被完整復制。
下面的這個例子可以說明這個特性:
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
聲明了一個常量hd,是Resolution的實例化,寬度是1920,高度是1080,然后聲明了一個變量cinema,和hd相同。這個時候表明,cinema和hd是兩個實例,雖然他們的寬度都是1920,高度都是1080。
如果把cinema的寬度更改為2048,hd的寬度不會變化,依然是1920
cinema.width = 2048
println("cinema is now /(cinema.width) pixels wide")
// prints "cinema is now 2048 pixels wide"
println("hd is still /(hd.width) pixels wide")
// prints "hd is still 1920 pixels wide"
這表明當hd被賦值給cinema時,是完整地復制了一個全新的Resolution結構給cinema,所以當cinema的屬性被修改時,hd的屬性不會變化。
下面的例子演示的是枚舉類型:
enum CompassPoint {
case North, South, East, West
}
var currentDirection = CompassPoint.West
let rememberedDirection = currentDirection
currentDirection = .East
if rememberedDirection == .West {
println("The remembered direction is still .West")
}
// prints "The remembered direction is still .West"
盡管經過幾次賦值,rememberedDirection依然沒有變化,這是因為在每一次賦值過程中,都是將數值類型完整地復制了過來。
3、類是引用類型
和數值類型不同引用類型不會復制整個實例,當它被賦值給另外一個常量或者變量的時候,而是會建立一個和已有的實例相關的引用來表示它。
下面是引用的示例,VideoMode被定義為一個類:
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
分別將這個實例tenEighty的四個屬性初始化,然后tenEighty被賦值給了另外一個叫alsoTenEighty的常量,然后alsoTenEighty的frameRate被修改了
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
由于類是一個引用類型,所以tenEighty和alsoTenEighty實際上是同一個實例,僅僅只是使用了不同的名稱而已,我們通過檢查frameRate可以證明這個問題:
println("The frameRate property of tenEighty is now /(tenEighty.frameRate)")
// prints "The frameRate property of tenEighty is now 30.0"
注意到tenEighty和alsoTenEighty是被定義為常量的,而不是變量。但是我們還是可以改變他們的屬性值,這是因為它們本身實際上沒有改變,它們并沒有保存這個VideoMode的實例,僅僅只是引用了一個VideoMode實例,而我們修改的也是它們引用的實例中的屬性。
特征操作
因為類是引用類型,那么就可能存在多個常量或者變量只想同一個類的實例(這對于數值類型的結構和枚舉是不成立的)。
可以通過如下兩個操作來判斷兩個常量或者變量是否引用的是同一個類的實例:
相同的實例(===)
不同的實例(!==)
使用這些操作可以檢查:
if tenEighty === alsoTenEighty {
println("tenEighty and alsoTenEighty refer to the same Resolution instance.")
}
// prints "tenEighty and alsoTenEighty refer to the same Resolution instance."
注意是相同的實例判斷使用三個連續的等號,這和相等(兩個等號)是不同的
實例相同表示的是兩個變量或者常量所引用的是同一個類的實例
相等是指兩個實例在數值上的相等,或者相同。
當你定義一個類的時候,就需要說明什么樣的時候是兩個類相等,什么時候是兩個類不相等。更多內容可以從相等操作一章中獲得。
指針
如果你有C,C++或者Objective-C的編程經驗,你一定知道在這些語言中使用指針來引用一個內存地址。Swift中引用一個實例的常量或變量跟C中的指針類似,但是不是一個直接指向內存地址的指針,也不需要使用*記號表示你正在定義一個引用。Swift中引用和其它變量,常量的定義方法相同。
4、如何選擇使用類還是結構
在代碼中可以選擇類或者結構來實現你所需要的代碼塊,完成相應的功能。但是結構實例傳遞的是值,而類實例傳遞的是引用。那么對于不同的任務,應該考慮到數據結構和功能的需求不同,從而選擇不同的實例。
一般來說,下面的一個或多個條件滿足時,應當選擇創建一個結構:
結構主要是用來封裝一些簡單的數據值
當賦值或者傳遞的時候更希望這些封裝的數據被賦值,而不是被引用過去
所有被結構存儲的屬性本身也是數值類型
結構不需要被另外一個類型繼承或者完成其它行為
一些比較好的使用結構的例子:
一個幾何形狀的尺寸,可能包括寬度,高度或者其它屬性,每個屬性都是Double類型的
一個序列的對應關系,可能包括開始start和長度length屬性,每個屬性都是Int類型的
3D坐標系中的一個點,包括x,y和z坐標,都是Double類型
在其它情況下,類會是更好的選擇。也就是說一般情況下,自定義的一些數據結構一般都會被定義為類。
5、集合類型的賦值和復制操作
Swift中,數組Array和字典Dictionary是用結構來實現的,但是數組與字典和其它結構在進行賦值或者作為參數傳遞給函數的時候有一些不同。
并且數組和字典的這些操作,又與Foundation中的NSArray和NSDictionary不同,它們是用類來實現的。
注意:下面的小節將會介紹數組,字典,字符串等的復制操作。這些復制操作看起來都已經發生,但是Swift只會在確實需要復制的時候才會完整復制,從而達到最優的性能。
字典的賦值和復制操作
每次將一個字典Dictionary類型賦值給一個常量或者變量,或者作為參數傳遞給函數時,字典會在賦值或者函數調用時才會被復制。這個過程在上面的小節:結構和枚舉是數值類型中描述了。
如果字典中的鍵值是數值類型(結構或者枚舉),它們在賦值的時候會同時被復制。相反,如果是引用類型(類或者函數),引用本身將會被復制,而不是類實例或者函數本身。字典的這種復制方式和結構相同。
下面的例子演示的是一個叫ages的字典,存儲了一些人名和年齡的對應關系,當賦值給copiedAges的時候,里面的數值同時被完整復制。當改變復制了的數值的時候,原有的數值不會變化,如下例子:
var ages = ["Peter": 23, "Wei": 35, "Anish": 65, "Katya": 19]
var copiedAges = ages
這個字典的鍵是字符串String類型,值是Int類型,都是數值類型,那么在賦值的時候都會被完整復制。
copiedAges["Peter"] = 24
println(ages["Peter"])
// prints "23"
數組的賦值和復制操作
和字典Dictionary類型比起來,數組Array的賦值和復制操作就更加復雜。Array類型和C語言中的類似,僅僅只會在需要的時候才會完整復制數組的值。
如果將一個數組賦值給一個常量或者變量,或者作為一個參數傳遞給函數,復制在賦值和函數調用的時候并不會發生。這兩個數組將會共享一個元素序列,如果你修改了其中一個,另外一個也將會改變。
對于數組來說,復制只會在你進行了一個可能會修改數組長度操作時才會發生。包括拼接,添加或者移除元素等等。當復制實際發生的時候,才會像字典的賦值和復制操作一樣。
下面的例子演示了數組的賦值操作:
var a = [1, 2, 3]
var b = a
var c = a
數組a被賦值給了b和c,然后輸出相同的下標會發現:
println(a[0])
// 1
println(b[0])
// 1
println(c[0])
// 1
如果改變a中的某個值,會發現b和c中的數值也會跟著改變,因為賦值操作沒有改變數組的長度:
a[0] = 42
println(a[0])
// 42
println(b[0])
// 42
println(c[0])
// 42
但是,如果在a中添加一個新的元素,那么就改變了數組的長度,這個時候就會發生實際的復制操作。如果再改變a中元素的值,b和c中的元素將不會發生改變:
a.append(4)
a[0] = 777
println(a[0])
// 777
println(b[0])
// 42
println(c[0])
// 42
設置數組是唯一的
如果可以在對數組進行修改前,將它設置為唯一的就最好了。我們可以通過使用unshare方法來將數組自行拷貝出來,成為一個唯一的實體。
如果多個變量引用了同一個數組,可以使用unshare方法來完成一次“獨立”
b.unshare()
這時候如果再修改b的值,c的值也不會再受影響
b[0] = -105
println(a[0])
// 777
println(b[0])
// -105
println(c[0])
// 42
檢查兩個數組時候共用了相同的元素
使用實例相等操作符來判斷兩個數組是否共用了元素(===和!===)
下面這個例子演示的就是判斷是否共用元素:
if b === c {
println("b and c still share the same array elements.")
} else {
println("b and c now refer to two independent sets of array elements.")
}
// prints "b and c now refer to two independent sets of array elements."
也可以使用這個操作來判斷兩個子數組是否有共用的元素:
if b[0...1] === b[0...1] {
println("These two subarrays share the same elements.")
} else {
println("These two subarrays do not share the same elements.")
}
// prints "These two subarrays share the same elements."
強制數組拷貝
通過調用數組的copy方法來完成強制拷貝。這個方法將會完整復制一個數組到新的數組中。
下面的例子中這個叫names的數組會被完整拷貝到copiedNames中去。
var names = ["Mohsen", "Hilary", "Justyn", "Amy", "Rich", "Graham", "Vic"]
var copiedNames = names.copy()
通過改變copiedNames的值可以驗證,數組已經被完整拷貝,不會影響到之前的數組:
copiedNames[0] = "Mo"
println(names[0])
// prints "Mohsen"
注意:如果你不確定你需要的數組是否是獨立的,那么僅僅使用unshare就可以了。而copy方法不管當前是不是獨立的,都會完整拷貝一次,哪怕這個數組已經是unshare的了。