Swift - 结构体优化- 写时复制

我们知道Swift中推荐使用具有值语义的结构体,而不是具有可变性的引用语义.

   let arr = [1,2,4,3]
   var arr2 = arr // 这一步会复制arr中的内容给arr2

Swift标准库中Array结构体是具有写时复制(copy-on-write)的.
如上代码在创建一个新Array变量并且将arr赋值给它的时候,会复制这个arr来确保它们各自独立. (这是我们平常的理解)

但是如上这种情况下,并没有对arr2进行任何的操作,arr2与arr独立开似乎并没有什么作用.这种情况下因为赋值进行复制一份内容是比较耗费性能的事.
所以通过写时复制要实现的就是,起初给arr2赋值后,arr和arr2的内容的内存地址是相同的.这两个数组共享了它们的存储部分.不过当我们改变arr2的时候这个内存会被检测到,这个时候再对这块内存的内容进行复制.. 因为复制操作只在必要的时候发生,节省了性能消耗.

来验证一下

按照"写时复制"的要求,上面代码 arr 和 arr2中的元素的内存地址是同一块的,

// 在两行代码后打断点,分别打印两个数组第一个元素的地址
(lldb) po withUnsafePointer(to: &arr[0], {$0})
▿ 0x000060400046a920
  - pointerValue : 105827998804256

(lldb) po withUnsafePointer(to: &arr2[0], {$0})
▿ 0x000060400046a920
  - pointerValue : 105827998804256

虽然arr和arr2是两个不同的结构体了,arr2的内容并没有真正复制一份.

当我们增加一段修改arr2的代码之后

let arr = [1,2,4,3]
var arr2 = arr
arr2[1] = 3

我们在代码执行后打印它们的第0个元素的地址

(lldb) po withUnsafePointer(to: &arr[0], {$0})
▿ 0x000060400026db60
  - pointerValue : 105827996719968

(lldb) po withUnsafePointer(to: &arr2[0], {$0})
▿ 0x000060400026daa0
  - pointerValue : 105827996719776

虽然我们修改的是数组中第一个元素,但是第0个元素和整个内容的地址都变了,可见它的内容确实在修改之前全部复制了一份..

自定义结构体实现写时复制

Swift标准库中的集合类型是已经实现了写时复制的.但是如果我们自己创建一个结构体,它并不免费具备这种特性.

struct MyData {
    fileprivate(set) var data : NSMutableData

    init(_ data: Data) {
        self.data = NSMutableData(data: data).mutableCopy() as! NSMutableData
    }
    func append(_ otherData: Data) {
        data.append(otherData)
    }
}

模仿系统中Data集合的操作,我们自定义了一个MyData结构体..获取其中data数据是通过只读的data属性.
当我们创建下列代码:

let theData = Data(base64Encoded: "wAEP/w==")!
let aData = MyData(theData)
var bData = aData
bData.append(theData)

因为将aData赋值给bData的时候是进行的浅复制.所以MyData中的data对象本身不会被复制,而只有对象的引用会被复制.在给bData进行append操作后

 po aData.data
<c0010fff c0010fff>
//// 我们发现aData也发生了变化

这不是我们想要的目的,我们现在只是创建了一个具有值语义的结构体.我们想要结构体在复制之后不会彼此间相互独立.并且在写入操作时候再进行必要的复制.
所以我们修改代码:

struct MyData {
    fileprivate(set) var data : NSMutableData
    fileprivate var dataForWriting: NSMutableData {
        mutating get {
            data = data.mutableCopy() as! NSMutableData
            return data
        }
    }
    init(_ data: Data) {
        self.data = NSMutableData(data: data).mutableCopy() as! NSMutableData
    }
    mutating func append(_ otherData: Data) {
    // 每次进行添加操作前调用dataForWriting的get方法
      对data进行复制,来保证与之前的对象独立.
        dataForWriting.append(otherData)
    }
}

我们引入了一个dataForWriting计算型属性来间接修改data.而不是直接修改data. 每次调用append进行"写入"操作的时候便会调动这个计算属性对data复制一份. 如此这个结构体遍完全的具有了值语义.. 而且是在append的时候才进行的复制.

性能优化

但是每次进行append操作都会复制一次原先的data,这样显然是效率极低的. 如果我们已经发现属性中的data属性只有自己引用,修改它的值不会影响到其它任何结构体..那么我们完全就可以直接修改它不需要频繁的复制了.
在Swift中就有一个函数可以检测某一个引用的唯一性.将一个Swift类传递给这个函数,如果没有其它强引用变量指向它,函数将返回true,如果还有其它引用指向它则返回false..对于OC的类它会直接返回false.所以直接对NSmutableData使用这个函数没有意义..我们可以创建一个Swift类,来将任意的OC对象封装到这个类中..

// Swift的class容器,传入一个OC类型
class Box<T> {
    var unbox: T
    init(_ value: T) {
        self.unbox = value
    }
}

接着我们重构一下MyData类

struct MyData {
    fileprivate(set) var data : Box<NSMutableData>
    fileprivate var dataForWriting: NSMutableData {
        mutating get {
            // 检验 data是否是唯一引用
            if !isKnownUniquelyReferenced(&data) {
                data = Box(data.unbox.mutableCopy() as! NSMutableData)
            }
            return data.unbox
        }
    }
    init(_ data: Data) {
        self.data = Box(NSMutableData(data: data).mutableCopy() as! NSMutableData)
    }
    mutating func append(_ otherData: Data) {
        dataForWriting.append(otherData)
    }
}

看起来是麻烦了一点.不过这样才能保证结构体的高效操作..

   let aData = MyData(theData)
  var bData = aData
  for _ in 0 ..< 5 {
       bData.append(theData)
  }

接着我们执行5次append操作..发现data的mutablecopy只执行了一次.因为拷贝一次后它的引用者也就只有这个结构体本身了.

虽然我们在创建结构体的时候大部分时候属性中都是一些值语义的..我们要确保结构体的不可变性.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容

  • 本文为转载: 作者:zyydeveloper 链接:http://www.jianshu.com/p/5f776a...
    Buddha_like阅读 868评论 0 2
  • 转载自:https://github.com/Tim9Liu9/TimLiu-iOS 目录 UI下拉刷新模糊效果A...
    袁俊亮技术博客阅读 11,915评论 9 105
  • 最近一直在学四吃炒饭,炒饭师傅每天和我边聊天边做饭,看他做饭的认真细致的样子简直像在欣赏一件艺术品的诞生。师傅每次...
    Miss霏霏阅读 185评论 0 0
  • 写啥?看看友人写的,再看看自己的。嗨,还是算了吧,人家的叫文章,我的叫流水账。没有情感,没有思想的文字是僵死的,我...
    书窗映月阅读 88评论 0 0
  • 良子,你知道么,我是很想很想和你聊聊天的。聊点什么呢?就聊一聊我吧! 我这个人是消极又积极的矛盾体,所以你们看到的...
    简西云阅读 250评论 0 0