原文地址:在Swift結(jié)構(gòu)體中實(shí)現(xiàn)寫(xiě)時(shí)復(fù)制
結(jié)構(gòu)體(Struct)在Swift中占有重要地位,在Swift標(biāo)準(zhǔn)庫(kù)中,大約有90%的公開(kāi)類型都是結(jié)構(gòu)體,包括我們常用的Array、String、Dictionary。結(jié)構(gòu)體相比類,一個(gè)最重要的特性就是它是值類型,而類似引用類型。值類型是通過(guò)復(fù)制值來(lái)賦值的,而不是引用同一個(gè)內(nèi)存地址,這樣就不存在數(shù)據(jù)共享的問(wèn)題,能防止意外的數(shù)據(jù)改變,并且它是線程安全的。
舉一個(gè)很簡(jiǎn)單的例子,在objc中,數(shù)組是類,是引用類型,在Swift中,數(shù)組是結(jié)構(gòu)體,是值類型。因此下面的代碼中:
let array1 = NSMutableArray(array: ["lihua", "liming"])
let array2 = array1
array1.addObject("xiaowang")
array2
array1和array2最后都變成了["lihua", "liming", "xiaowang"],也就是array1的改變會(huì)導(dǎo)致array2也發(fā)生改變,因?yàn)樗鼈儍蓚€(gè)都是引用類型,并且都引用了同一個(gè)內(nèi)存地址。
而在Swift中,就不存在這樣的問(wèn)題:
var array3 = ["lihua", "liming"]
var array4 = array3
array3.append("xiaowang")
array4
這段代碼執(zhí)行后,array3變成了["lihua", "liming", "xiaowang"],而array4還是["lihua", "liming"]。這就是結(jié)構(gòu)體和類的最大區(qū)別。
那么,是不是每次將struct賦值給其它變量或者傳遞給函數(shù)時(shí)都會(huì)發(fā)生復(fù)制呢。答案是否定的,在Swift中的Array、Dictionary、String這些類型中,盡管它們都是值類型,但在Swift的具體實(shí)現(xiàn)中做了優(yōu)化,可以避免不必要的復(fù)制。在《The Swift Programming Language (Swift 2.2)》一書(shū)的“Classes and Structures”一章末尾寫(xiě)道:
The description above refers to the “copying” of strings, arrays, and dictionaries. The behavior you see in your code will always be as if a copy took place. However, Swift only performs an actual copy behind the scenes when it is absolutely necessary to do so. Swift manages all value copying to ensure optimal performance, and you should not avoid assignment to try to preempt this optimization.
在Swift中采用的優(yōu)化方式叫做寫(xiě)時(shí)復(fù)制技術(shù),簡(jiǎn)單的說(shuō)就是,只有當(dāng)一個(gè)結(jié)構(gòu)體發(fā)生了寫(xiě)入行為時(shí)才會(huì)有復(fù)制行為。具體的做法就是,在結(jié)構(gòu)體內(nèi)部用一個(gè)引用類型來(lái)存儲(chǔ)實(shí)際的數(shù)據(jù),在不進(jìn)行寫(xiě)入操作的普通傳遞過(guò)程中,都是將內(nèi)部的reference的應(yīng)用計(jì)數(shù)+1,在進(jìn)行寫(xiě)入操作時(shí),對(duì)內(nèi)部的reference做一次copy操作用來(lái)存儲(chǔ)新的數(shù)據(jù),防止和之前的reference產(chǎn)生意外的數(shù)據(jù)共享。
在Swift中有一個(gè)方法:isUniquelyReferencedNonObjC(Swift 2.2),在Swift3中這個(gè)函數(shù)變成了這樣:isKnownUniquelyReferenced,他能檢查一個(gè)類的實(shí)例是不是唯一的引用,如果是,我們就不需要對(duì)結(jié)構(gòu)體實(shí)例進(jìn)行復(fù)制,如果不是,說(shuō)明對(duì)象被不同的結(jié)構(gòu)體共享,這時(shí)對(duì)它進(jìn)行更改就需要進(jìn)行復(fù)制。
但這個(gè)函數(shù)只對(duì)Swift對(duì)象有用,如果要用在Objective-C對(duì)象上,可以將OC對(duì)象用Swift進(jìn)行一次封裝。
下面是《Advanced Swift》書(shū)中的一個(gè)實(shí)現(xiàn)寫(xiě)時(shí)復(fù)制技術(shù)的代碼實(shí)例,我已經(jīng)把它轉(zhuǎn)為Swift3了:
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)
}()
fileprivate var filter: CIFilter {
get { return boxedFilter.unbox }
set { boxedFilter = Box(newValue) }
}
private var filterForWriting: CIFilter {
mutating get {
if !isKnownUniquelyReferenced(&boxedFilter) {
filter = filter.copy() as! CIFilter
}
return filter
}
}
var inputImage: CIImage {
get { return filter.value(forKey: kCIInputImageKey) as! CIImage }
set { filterForWriting.setValue(newValue, forKey: kCIInputImageKey) }
}
var radius: Double {
get { return filter.value(forKey: kCIInputRadiusKey) as! Double }
set { filterForWriting.setValue(newValue, forKey: kCIInputRadiusKey) }
}
}
extension GaussianBlur {
var outputImage: CIImage? {
return filter.outputImage
}
}