您当前所在位置: > 网游 > 综合热点

高效使用集合 Using Collections Effectively

时间:2018-08-31 18:42:47  来源:  作者:网络
请注意:本文为编辑制作专题转载的资讯,页面显示的时间仅为生成静态页面时间而非具体内容事件发生的时间,由此给您带来的不便敬请谅解!

一个没有集合的世界假如世界没有集合

假如没有Array

假如, 我们要定义一个熊, 我们可以let bear1 = "Grizzly"

如果, 我们要四个呢?let bear1 = "Grizzly" let bear2 = "Panda" let bear3 = "Polar" let bear4 = "Spectacled"

现在, 让我们尝试打印出来let bear1 = "Grizzly" let bear2 = "Panda" let bear3 = "Polar" let bear4 = "Spectacled" print("(bear1) bear") // Grizzly bear print("(bear2) bear") // Panda bearprint("(bear3) bear") // Polar bear print("(bear4) bear") // Spectacled bear

我们需要不断的做重复的事情

假如没有Dictionary

让我们继续上面的例子, 现在我们有一个记录每个熊的喜好的函数func habitat(for bear: String) -> String? { if bear == "Polar" { return "Arctic" } else if bear == "Grizzly" { return "Forest" } else if bear == "Brown" { return "Forest" } else if /* all the other bears */ ... return nil }

我们仍然有很多重复的事情需要做.一个拥有集合的世界

当我们引入集合的概念, 上面的事情, 就变得清晰明亮了let bear = ["Grizzly", "Panda", "Polar", "Spectacled"] let habitats = ["Grizzly": "Forest", "Polar": "Arctic"] for bear in bears { print("(bear) Bear") } let bear = bears[2] let habitat = habitats[bear] ?? "" print("(bear) bears live in the (habitat)")

集合有很多相同的行为和属性, 于是我们把它们抽象出来, 作为集合协议.访问集合

集合有很多应用, 例如链表, 红黑树等等, 他们都有一个起始位置, 一个终止位置, 可以通过下表访问任意位置的元素

代码展示protocol Collection : Sequence { // 集合中元素的类型 associatedtype Element // 索引类型, 需要遵守Comparable协议 associatedtype Index : Comparable // 遍历时所用到的方法了, 即通过索引查询到对应的元素 subscript(position: Index) -> Element { get } // 开始索引 var startIndex: Index { get } // 结束索引 var endIndex: Index { get } // 通过一个索引, 获取它后面的索引 func index(after i: Index) -> Index }

这里用到了associatedtype关键字, 在Swift协议定义的时候, 会看到使用这个关键字, 你可以认为这是一个占位符, 具体的类型直到被用到的时候才会确定. 但是有时候我们需要规定这个占位符要有一些能力, 比如这里的Index, 他就需要遵守Comparable协议.

接下来的一个方法, 是通过索引访问到对应的元素

startIndex, endIndex, 表示了集合的边界

最后这个方法, 可以通过索引来获取下一个元素的索引.集合的扩展

这张图是一些集合的扩展, 放眼望去, 有一些我们会经常用到的方法或属性, 例如:

first, 集合的第一个元素

last, 集合的最后一个元素

isEmpty, 集合是否是空的

count, 集合元素个数

用于遍历的

forEach

makeIterator()

一些高阶函数

map

filter

reduce

当然, 我们也可以做一些自己的扩展

扩展DIY

系统提供的遍历是逐个元素遍历, 现在, 让我们来实现一个隔元素访问的功能.

WX20180614-211836@2x.pngextension Collection { // 扩展集合协议 func everyOther(_ body: (Element) -> Void) { // 获取首元素索引 let start = self.startIndex // 获取末尾元素索引 let end = self.endIndex var iter = start // 未走到末尾 while iter != end { // 执行外部的闭包 body(self[iter]) // 获取当前元素的下一个索引 let next = index(after: iter) // 索引是否走到末尾 if next == end { break } // 将当前索引指向next的下一个 iter = index(after: next) } } } (1...10).everyOther { print($0) }继承关系

实际上, 除了Collection以外, 我们还有很多继承自Collection的协议, 例如,

BidirectionalCollection 双向集合, 可以向前访问元素, 当然, 它继承自Collection, 也可以向后访问元素.

RandomAccessCollection 随机访问集合, 提供了复杂度为O(1)的访问方法, 当然, 它也有向前和向后访问元素的能力

MutableCollection 可变集合, 提供了修改集合元素的能力

RangeReplaceCollection 范围替换集合, 提供了通过指定范围替换元素的能力索引

我们可以通过索引的方式来访问集合中的元素, 例如, 我们要访问集合中的第一个元素,

访问第一个元素

通过下标进行直接访问

使用array[0]访问第一个元素, 当然没有问题, 可是如果我们扩展开来, 如果给的集合不是数组, 而是一个set, 那么, 这样的方式就行不通了.

通过索引进行访问

使用set[set.startIndex]进行访问, 这样就可以了, 但是, 你需要注意潜在的问题, 你需要判空, 需要判断越界, 诸如此类

first

好在苹果的工程师为这些常用的元素访问留了方便的方式我们可以使用set.first进行获取. 而且不用担心那些潜在的问题

访问第二个元素

当然, 苹果工程师也无法预测到所有的情况, 比如我们想要获得第二个元素. 这时候, 就需要进行DIY了

通过下标直接访问

显然, 不能通过这两种方式来进行获取, 因为我们之前说到, Index这个占位符并不一定是Int, 而是一个遵守了Comparable的类型.

切片

那么, 对于上面的例子来说, 我们有没有更加易于维护或者说更加优雅的实现方式呢?

假如我们去掉首元素, 然后再获取新得到的集合的第一个元素, 那么, 就可以优雅的实现了.

那么, dropFirst所产生的对象, 就是一个切片, 在WWDC中, 将它比喻成了一个buffer.

注意内存

值得注意的是, 持有切片, 将使得即便将原来的集合置空, 内存也不会释放.

这里, 我的理解是这样的, 切片是一个 原有集合 + 映射关系 的产物. 所以, 除非将切片也置空, 否则, 原有集合并不会被释放.

共享索引

延迟计算

经过这样的一套操作, 我们计算了4004个元素, 如果我们后面还有一些其他的操作, 更糟糕的是, 如果我们最终只是取取first, 这样, 前面生成的那些元素, 都成为了浪费.

这时, 我们可以通过lazy关键字, 可以规避这样的浪费

可以看到, 使用lazy后, 刚才的遍历过程, 变成了组织一个新集合的过程,

只有在first进行计算的时候, 才进行计算

让我们通过一个更直观的例子来体验

验证import UIKitclass ViewController: UIViewController { var arr = Array

() var result: Int? override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) for i in 0..<10000000 { arr.append(i) } } @IBAction func slice(_ sender: UIBarButtonItem) { result = arr.map { $0 * 2 }.map { $0 + 5 }.filter { $0 < 100 }.first print(result) }}

在这个例子中, 为了验证非lazy情况下浪费的资源包含哪些, 以及上文中提到的 切片是一个 原有集合 + 映射关系 的产物 的结论验证

我使用的模拟器进行测试, 在初始内存为125M, 当我点击按钮, 开始进行计算的时候, 可以看到CPU和内存都有飙升, 内存波动飙升到274M后, 下降到125M. CPU则飙升到100%后, 下降到0.

而使用lazy后, 内存和CPU几乎没有变化

使用情况

链式计算

仅仅需要求值结果中的某一部分

本身的计算不影响外部multi & safe

可变集合

使用了失效索引

复用写之前的索引

如何规避

在持有索引和切片时, 处理要谨慎

集合发生改变时, 要更新索引后再使用

在需要索引和切片的情况下才对其进行计算

多线程访问

如何规避

使用单线程进行访问

使用Thread Sanitizer其他建议

如果可以, 尽量使用带capacity的初始化函数去初始化你的集合, 因为这样节省一些不必要的内存开销, 虽然这并不能节省多少, 但是想象你的项目中有成千上万个集合对象, 他们可以省出一个相当可观的内存数量.桥接

Foundation Collection

值类型与引用类型

在swift 中的集合, 都是值类型, 为什么这么设计呢? 让我们先看一组图片

引用类型的操作

我们有一个集合x

当我们执行 let y = x 的时候, y指针会指向x所指向的内存空间

当我们继续执行append的时候, x和y所指的集合新增一个元素

值类型的操作

我们有一个集合x

当我们执行 let y = x 的时候, y指针会指向x所指向的内存空间

当我们继续执行append的时候, y所指向的集合将x内容拷贝进集合, 并将新元素放入集合

对于值类型来说, 这样有什么好处呢? 因为在现代CPU在设计的时候, 采用了缓存机制, 可以快速的访问连续区域的地址. 而值类型的这种操作, 各个元素之间的内存是相连的, 而引用类型的则不是.Swift与Foundation Collection的桥接

桥接就是把一种语言的某个类型转换为另一种语言的某个类型. 桥接在swift与OC之间是双向的, 也是必要的, 当然, 也是有一些资源开销的, 可以通过Instrument进行测量

这里的桥接发生在

NSMutableAttributedString取string上, return bridge.

需要传入一个NSString, 的参数类型桥接 param bridge

虽然在这里也发生了桥接, 但是集合可以忽略不计

建议

建议在开发过程中, 尽量避免Swift的collection与NS以及CF的collection进行混用. 由此, 笔者猜测, swift中的类型去掉NS头的原因, 就是为了方便辨认是否需要桥接, 当然, 如果确切的知道是否存在桥接的损耗, 还是需要通过Instrument进行测量.最后

相关下载

玩家评论

S9小组赛SKT首发名单公布:Effort首发,延续夏季夺冠阵容

S9全球总决赛明天晚上8点就鸣锣开打了,赛事官推在今天已经向观众确定了小组赛首日的首发名单,并且也公布了SKT战队的首发阵容,下面就让我们一起来看一下SKT详情>>

阅读: 32
日期: 2019-10-11
S9小组赛首日五队首发名单:iG首发打野Leyan,SKT首发辅助Effort_vs

原标题:S9小组赛首日五队首发名单:iG首发打野Leyan,SKT首发辅助Effort 北京时间10月11日,lolesports官方推特公布了2019全球总决赛小组赛第一日五支战详情>>

阅读: 6
日期: 2019-10-11
S9小组赛首日五队首发名单:iG首发打野Leyan,SKT首发辅助Effort

原标题:S9小组赛首日五队首发名单:iG首发打野Leyan,SKT首发辅助Effort 北京时间10月11日,lolesports官方推特公布了2019全球总决赛小组赛第一日五支战队详情>>

阅读: 13
日期: 2019-10-11
《NBA2K17》efeab30c连不上服务器怎么办 出现efeab30c解决方法

小编为您搜罗的答案:1、发生这种事情的原因:在2k服务器有信息更新时(如游戏更新,切换电脑硬件)被玩家强制取消后,会使玩家强制离线,而转为后台下载更新,需要下载完毕后才可以进行线上游戏,而破解版(盗版详情>>

阅读: 6
日期: 2018-09-14
NBA2K19错误代码efeab30c 如何解决?

最近NBA2K19与小伙伴们见面,然而在游戏过程中依然和去年的NBA2K18一样,启动游戏就提示错误代码详情>>

阅读: 0
日期: 2018-09-10
有没有类似EFUN手游大赛的活动可以参加啊?EFUN之前城市赛看了看视频觉得很有意思,但是没去成。

小编为您搜罗的答案:EFUN手游大赛还有总决赛啊,具体时间地点你可以关注爱游戏的官方微博微信,都会第一时间放出消息的。小编为您搜罗的答案:嗯,那几个视频我也看了,很有意思。我打听过了,接下来要举办EF详情>>

阅读: 4
日期: 2018-08-23
《NBA2K17》错误代码efeab30c连不上服务器解决方法

  《NBA2K17》错误代码efeab30c是大家会遇到的问题,而很多玩家表示不知怎么才能连上服务器,今天小编带来“灬铭泽天下灬”分享的《NBA2K17》错误代码详情>>

阅读: 1
日期: 2018-04-14
精彩推荐