Swift Copy-On-Write

一.堆棧

棧是一塊空間較小但是運行速度很快的內存區域,棧上的內存分配遵循后進先出的原則,通過移動棧的尾指針實現push和pop操作。
堆是內存中的另外一塊,空間比棧大很多,但是運行速度比棧要慢。但是堆可以動態分配內存。堆的內存分配比較復雜,系統需要在堆上不斷尋找不再需要的內存然后進行回收。在ARC中上述過程是自動的。另外在多線程環境中,多個線程會共享堆內存。為了確保線程安全,堆會對資源進行加鎖操作。但是加鎖是很耗費性能的,你在堆上所獲得的數據安全性實際上是在犧牲性能的代價下得來的。

二.swift中的值類型和引用類型

1.值類型

在Swift中,值類型有兩種。一種是定長值類型,比如數值類型Int,Double,Float,還有一些只包含定長值類型的結構體(CGPoint)等等;另外一種叫做變長值類型,比如String,數組,字典等等。定長值類型都會保存在棧上,而變長值類型則會分配堆內存。
值類型的實例(結構體)只會在棧上保存它內部的存儲屬性,并且通過=賦值的實例彼此的存儲是獨立的。也就是我們所說的拷貝。如下:

struct Point{
  var x,y:Double
}
let point1 = Point(x:3,y:5)
var point2 = point1

point1和point2會被分配到棧上,并且會分別為point1和point2分配內存空間。在語句point2 = point1的時候,point1進行了拷貝。因為定長值類型的空間是固定的,所以這種拷貝的開銷很小。

2.引用類型

引用類型并不會直接保存在棧上,還是以上述Point為例,如果把Point修改為類,并生成兩個引用point1和point2,這時系統會在棧上開辟兩個指針長度來保存point1和ponit2指針,棧上的指針負責去堆上找對應的對象。point1和point2所指向的實例的存儲屬性會保存在堆上。
在棧上生成point1指針后,指針內容是空的,接下來會去堆上分配內存,首先會對堆加鎖,找到尺寸合適的空間,然后分配目標內存并解除堆的鎖定,將堆內存片段的首地址保存在棧的指針中。相比在棧上保存point1和point2的指針,堆上需要的空間更大。除了x和y的空間,在頭部還有8個字節的空間,一個用來索引類的類型信息的指針地址,一個用來保存對象的引用計數。當使用=賦值時,棧上會生成point2指針,point1和point2指針指向同一個堆地址
引用類型的賦值不會發生拷貝。所以無論改變point1或者是point2的屬性,改變的都是同一塊堆地址上的屬性。這里要特別說明let和var。swift提供了let和var來限制對象的可變性和不可變性,但是對于某個實例,有意義的是其內部屬性。如果你用let聲明一個引用類型對象,你只能保證它的指針地址不能被改變,但是不能約束它的內部屬性。舉個例子:

//這里的Point是Class
//聲明point1和point2指向不同的內存地址
let point1 = Point(x:3,y:5)
let point2 = Point(x:5,y:1)
point1 = point2 //發生編譯錯誤,不能修改point1的指針
point1.x = 0 //因為x是用var定義的所以可以修改
 //這里point1.x == 0

我們把多個引用指向同一塊內存稱為資源共享。不過在實際開發過程中,很多時候我們并不想讓point1和point2資源共享,這樣會造成很多難以判斷的錯誤。在Swift中,一種新的類型來專門解決共享的問題,就是我們所說的變長值類型。剛才講值類型的時候有提過定長值類型的內存分配,那么變長值類型是什么樣的?這就是我們今天的主題Copy-On-Write。

三.Copy-On-Write

因為棧上的空間是連續的,你總是通過移動棧尾指針去開辟和釋放棧內存,而變長值類型中有一些成員在初始化的時候并不能確定它所占用的內存。比如集合類型,你可以隨時往里面添加和刪除元素,這會導致內存的增加和減少。類似的還有字符串,在內存中儲存字符串實際上是存儲的每一個字符,所以對于變長值類型并不能把全部內容都保存在棧上。在Swift中用了一種很巧妙的技術來實現變長值類型,那就是Copy-On-Write。

Copy-On-Write故名思議就是寫時復制,當我們對變量進行寫操作的時候會觸發拷貝操作。但是我們也不能在每一次寫入的時候都拷貝,思考一下,如果該變量的引用計數只有1,那就沒有任何拷貝的必要。所以在拷貝前我們需要檢測變量的引用計數是否唯一。在swift中提供了isKnownUniquelyReferenced,它能檢查一個類的實例是不是唯一的引用。然而這個方法只能對Swift的類使用,所以對于不是Swift的類我們需要在外面包裝一下。下面我們看代碼:

import UIKit
//聲明swift包裝類,用于包裝OC對象UIBezierPath
class Box<T>{
    var rawValue:T
    init(rawValue:T) {
        self.rawValue = rawValue
    }
}
struct BezierPath{
    private var _path = Box.init(rawValue: UIBezierPath())
    var pathForReading:Box<UIBezierPath>{
        return _path
    }
    var pathForWriting:Box<UIBezierPath>{
        //mutating 聲明的方法可以修改結構體中變量
        //isKnownUniquelyReferenced 檢測引用類型的引用是否唯一 但是只對swift類有用 這里我們針對的對象是UIBezierPath 所以我們需要用Swift的類包裝一下 在這里我們聲明了Box類
        mutating get{
            if !isKnownUniquelyReferenced(&_path){
                _path = Box.init(rawValue: _path.rawValue.copy() as! UIBezierPath)
                print("拷貝")
                return _path
            }
            print("未拷貝")
            return _path
        }
    }
}


class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        var bizer = BezierPath()
        bizer.pathForWriting.rawValue.lineWidth = 3
        //內部Box對象引用計數為1,不拷貝
        bizer.pathForWriting.rawValue.lineWidth = 10

        //Box對象引用計數+1
        //bizer和bizer1會共享內部Box對象
        //要注意這里的賦值把bizer進行了拷貝,但是其內部的引用屬性還是指向相同地址。
        var bizer1 = bizer
        
        bizer1.pathForWriting.rawValue.lineWidth = 5
        
        print(bizer.pathForReading.rawValue.lineWidth) //輸出10
        // Do any additional setup after loading the view, typically from a nib.
    }
}

相關的地方在代碼中都有給出注釋,所以在這里就不在贅述。

完!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,748評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,165評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,595評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,633評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,435評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,943評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,035評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,175評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,713評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,599評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,788評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,303評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,034評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,412評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,664評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,408評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,747評論 2 370

推薦閱讀更多精彩內容