結(jié)構(gòu)體和類模塊分兩篇筆記來學(xué)習(xí):
- 第一篇:
- 結(jié)構(gòu)體和類的區(qū)別
- 分析類和結(jié)構(gòu)體可變性
- 以一個具體的例子來學(xué)習(xí)使用類和結(jié)構(gòu)體的區(qū)別,以及如何使用寫時復(fù)制來解決結(jié)構(gòu)體內(nèi)部引用類型的復(fù)制
- 最后學(xué)習(xí)函數(shù)閉包的可變性
- 第二篇:
- 類和結(jié)構(gòu)體的內(nèi)存分析
- 何時使用類何時使用結(jié)構(gòu)體
本篇開始學(xué)習(xí)第一部分,go!
1.結(jié)構(gòu)體(和枚舉)和類的區(qū)別
結(jié)構(gòu)體 (和枚舉) 是值類型,而類是引用類型。在設(shè)計結(jié)構(gòu)體時,我們可以要求編譯器保證不可變性。而對于類來說,我們就得自己來確保這件事情。
內(nèi)存的管理方式有所不同。結(jié)構(gòu)體可以被直接持有及訪問,但是類的實例只能通過引用來間接地訪問。結(jié)構(gòu)體不會被引用,但是會被復(fù)制。也就是說,結(jié)構(gòu)體的持有者是唯一的,但是類卻能有很多個持有者。
除非一個類被標記為 final,否則它都是可以被繼承的。而結(jié)構(gòu)體 (以及枚舉) 是不能被繼承的。想要在不同的結(jié)構(gòu)體或者枚舉之間共享代碼,我們需要使用不同的技術(shù),比如像是組合,泛型,以及協(xié)議擴展等。
2.實體和值
實際開發(fā)中有些類型的對象我們需要控制它們的生命周期,或者比較兩個對象是否指向同一個內(nèi)存地址,比如文件句柄、通知中心、網(wǎng)絡(luò)接口、數(shù)據(jù)庫連接、ViewContrller等,這些都是引用類型。有些類型我們不需要聲明周期,比較的時候也只是需要比較所對應(yīng)的值是否相等,比如URL,二進制數(shù)據(jù),日期,錯誤,字符串,通知以及數(shù)字等,這些類型都是值類型,可以使用結(jié)構(gòu)體實現(xiàn)。
引用語義和值語義:當(dāng)我們將結(jié)構(gòu)體變量傳遞給一個函數(shù)時,函數(shù)將接收到結(jié)構(gòu)體的復(fù)制,它也只能改變它自己的這份復(fù)制。這叫做值語義 (value semantics),有時候也被叫做復(fù)制語義。而對于對象來說,它們是通過傳遞引用來工作的,因此類對象會擁有很多持有者,這被叫做引用語義
值類型的復(fù)制優(yōu)化和值語義的寫時復(fù)制:編譯器對結(jié)構(gòu)體的復(fù)制進行優(yōu)化,只是按照字節(jié)進行淺復(fù)制。寫時復(fù)制由開發(fā)者實現(xiàn),想要實現(xiàn)寫時復(fù)制,需要檢測所包含的類是否有共享的引用。
3.不可控制的可變性 (為何不用類)
Objective-C 的很多面向?qū)ο缶幊陶Z言種,數(shù)據(jù)默認是可變的,這在多線程編程里會造成非常大的麻煩,而解決的方法就是通過不可變性的值類型來保障線程安全。
-
類型的可變性無論是在oc還是swift中都是無法徹底控制的。
class File { let data: NSMutableData init(data: NSMutableData) { self.data = data } }雖然使用了let來控制不能改變屬性所指向的對象,但是可以修改當(dāng)前所指向?qū)ο蟮闹怠?/p>
4.值類型
引用一個例子做對比引用類型和值類型:
let inputParameters = [
kCIInputRadiusKey: 10,
kCIInputImageKey: image
]
let blurFilter = CIFilter(name: "CIGaussianBlur",
withInputParameters: inputParameters)!
let secondBlurFilter = blurFilter
secondBlurFilter.setValue(20, forKey: kCIInputRadiusKey)
CIFilter是CoreData里的一個濾鏡類,第二個濾鏡的配置變動會影響第一個濾鏡的配置??梢酝ㄟ^復(fù)制時手動復(fù)制來避免影響:
let otherBlurFilter = blurFilter.copy() as! CIFilter
otherBlurFilter.setValue(20, forKey: kCIInputRadiusKey)”
接下來使用結(jié)構(gòu)體來實現(xiàn):
struct GaussianBlur {
var inputImage: CIImage
var radius: Double
}
var blur1 = GaussianBlur(inputImage: image, radius: 10)
blur1.radius = 20
var blur2 = blur1
blur2.radius = 30”
這種復(fù)制聽上去有點浪費,但是編譯器會為我們優(yōu)化,此外如果如果使用寫時復(fù)制的話,實際對數(shù)據(jù)的復(fù)制只會在其中某個值實際改變的時候才會發(fā)生。其實這也是 Swift 數(shù)組的工作方式。接下來使用擴展對GaussianBlur 結(jié)構(gòu)體上添加一個 outputImage 屬性:
extension GaussianBlur {
var outputImage: CIImage {
let filter = CIFilter(name: "CIGaussianBlur", withInputParameters: [kCIInputImageKey: inputImage,kCIInputRadiusKey: radius])!
return filter.outputImage!
}
上面的代碼在每次 outputImage 被訪問時都會創(chuàng)建一個新的濾鏡。這并不是很高效的做法,如果我們多次訪問這個屬性,會有好多新的濾鏡被創(chuàng)建出來。
為了避免這種性能的浪費,采用另一種高效的做法,不像上面那樣將值存在結(jié)構(gòu)體中,這次采用直接存儲CIFilter實例,然后通過自定義屬性進行修改:
struct GaussianBlur {
private var filter: CIFilter
init(inputImage: CIImage, radius: Double) {
filter = CIFilter(name: "CIGaussianBlur", withInputParameters: [
kCIInputImageKey: inputImage,
kCIInputRadiusKey: radius
])!
}
}
接下來,我們實現(xiàn) inputImage 和 radius 屬性,來直接訪問和修改濾鏡的屬性:
extension GaussianBlur {
var inputImage: CIImage {
get { return filter.valueForKey(kCIInputImageKey) as! CIImage }
set { filter.setValue(newValue, forKey: kCIInputImageKey) }
}
var radius: Double {
get { return filter.valueForKey(kCIInputRadiusKey) as! Double }
set { filter.setValue(newValue, forKey: kCIInputRadiusKey) }
}
}
最后,要取出輸出圖像,我們可以直接使用濾鏡上的 outputImage 屬性:
extension GaussianBlur {
var outputImage: CIImage {
return filter.outputImage!
}
}
此時可以正常的滿足需求了:
var blur = GaussianBlur(inputImage: image, radius: 25)
blur.outputImage
但是如果復(fù)制結(jié)構(gòu)體的話就會有個問題:其中的所有值類型將被復(fù)制,而引用類型卻只有對于對象的引用會被復(fù)制,對象本身不被復(fù)制。如果對blur進行復(fù)制產(chǎn)生otherBlur時,blur和otherBlur中的濾鏡將指向同一個CIFilter實例!
5.寫時復(fù)制
Swift 在實現(xiàn)值語義的結(jié)構(gòu)體時,其底層使用了可變的對象,這也正是 Swift 中最強大的特性之一。我們從值語義里獲益良多,同時又可以保持代碼高效。然而,正如我們所看到的,這種做法可能導(dǎo)致意外的數(shù)據(jù)共享。
通過寫時復(fù)制這種技術(shù)可以實現(xiàn):在每次結(jié)構(gòu)體被改變時,去復(fù)制一個封裝后的對象,以此來避免共享。其實Swift 的很多數(shù)據(jù)結(jié)構(gòu)都是以這種方式工作的。
配合上一節(jié)的濾鏡例子,我們可以通過寫時復(fù)制來避免濾鏡的共享:
extension GaussianBlur {
private var filterForWriting: CIFilter {
mutating get {
filter = filter.copy() as! CIFilter
return filter
}
}
}
mutating修飾符作用是使方法能夠修改結(jié)構(gòu)體的變量。這讓我們得以更改濾鏡的設(shè)置方法,在改變值之前先進行復(fù)制:
extension GaussianBlur {
var inputImage: CIImage {
get { return filter.valueForKey(kCIInputImageKey) as! CIImage }
set {
filterForWriting.setValue(newValue, forKey: kCIInputImageKey)
}
}
var radius: Double {
get { return filter.valueForKey(kCIInputRadiusKey) as! Double }
set {
filterForWriting.setValue(newValue, forKey: kCIInputRadiusKey)
}
}
}
上面這種方式可以按照預(yù)想工作,但是卻帶來了性能上的代價:每次我們改變?yōu)V鏡值的時候,濾鏡都會被復(fù)制,即便是我們只是做一些本地修改,而沒有共享結(jié)構(gòu)體時,這個復(fù)制也會發(fā)生”
6.高效的寫時復(fù)制
在 Swift 中有一個方法,isUniquelyReferencedNonObjC,它會檢查一個類的實例是不是唯一的引用。我們可以使用它來在保證結(jié)構(gòu)體的值語義的同時,避免不必要的復(fù)制。
不幸的是,isUniquelyReferencedNonObjC 函數(shù)只對 Swift 對象有用,而 CIFilter 是一個 Objective-C 類。想要繞過這個限制,我們可以創(chuàng)建一個簡單的 Box 封裝類型,來把任意的 Swift 類封裝進去:
final class Box<A> {
var unbox: A
init(_ value: A) { unbox = value }
}
struct GaussianBlur {
private var boxedFilter: Box<CIFilter> = {
var filter = CIFilter(name: "CIGaussianBlur",withInputParameters: [:])!
filter.setDefaults()
return Box(filter)
}()
var filter: CIFilter {
get { return boxedFilter.unbox }
set { boxedFilter = Box(newValue) }
}
private var filterForWriting: CIFilter {
mutating get {
if !isUniquelyReferencedNonObjC(&boxedFilter) {
filter = filter.copy() as! CIFilter
}
return filter
}
}
}
這正是 Swift 數(shù)組內(nèi)部的工作方式。當(dāng)你創(chuàng)建一個新的數(shù)組的復(fù)制時,它背后的數(shù)據(jù)是一樣的。只有當(dāng)你要修改這個數(shù)組時,才會進行復(fù)制,這樣一來,對該數(shù)組的更改不會影響到其他的數(shù)組。當(dāng)在一個結(jié)構(gòu)體中使用類時,我們需要保證它確實是不可變的。如果辦不到這一點的話,我們就需要 (像上面那樣的) 額外的步驟。或者就干脆使用一個類,這樣我們的數(shù)據(jù)的使用者就不會期望它表現(xiàn)得像一個值。
7.閉包的可變性
閉包和函數(shù)也是引用類型的,如果進行復(fù)制,兩個函數(shù)或閉包對象將共享同樣的狀態(tài):
var i = 0
func uniqueInteger() -> Int {
i += 1
return i
}
let otherFunction: () -> Int = uniqueInteger
此時uniqueInteger和otherFunction將共同享用i變量。使用對閉包的再次封裝,使得返回享用共同變量的閉包的方式來解決問題:
func uniqueIntegerProvider() -> () -> Int {
var i = 0
return {
i += 1
return i
}
}