原文地址:在Swift結(jié)構(gòu)體中實(shí)現(xiàn)寫時(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”一章末尾寫道:
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)化方式叫做寫時(shí)復(fù)制技術(shù)媒区,簡(jiǎn)單的說(shuō)就是,只有當(dāng)一個(gè)結(jié)構(gòu)體發(fā)生了寫入行為時(shí)才會(huì)有復(fù)制行為掸犬。具體的做法就是袜漩,在結(jié)構(gòu)體內(nèi)部用一個(gè)引用類型來(lái)存儲(chǔ)實(shí)際的數(shù)據(jù),在不進(jìn)行寫入操作的普通傳遞過(guò)程中湾碎,都是將內(nèi)部的reference的應(yīng)用計(jì)數(shù)+1宙攻,在進(jìn)行寫入操作時(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)寫時(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
}
}