Swift 更高效地使用集合(Using Collections

2018-12-31  本文已影响33人  FicowShen

目录


基本原理



Collection协议

protocol Collection : Sequence {
    associatedtype Element
    associatedtype Index : Comparable

    subscript(position: Index) -> Element { get }

    var startIndex: Index { get }
    var endIndex: Index { get }

    func index(after i: Index) -> Index
 }



startIndex, endIndex, index(after: )以及subscript下标的使用示例:

extension 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 }
            iter = index(after: next)
        }
    }
}
(1...10).everyOther { print($0) }

各种集合协议之间的关系

Collections Protocol Hierarchy



索引和切片



首先介绍这两种集合协议

Bidirectional Collections:
func index(before: Self.Index) -> Self.Index

Random Access Collections:
// constant time
func index(_ idx: Index, offsetBy n: Int) -> Index
func distance(from start: Index, to end: Index) -> Int

Swift中常见的集合类型

Array, Set, Dictionary



以获取集合中的第二个元素为例,讲解索引和切片的用法:

// 多次判定,然后通过索引获取第二个元素
extension Collection { 
    var second: Element? {
        // Is the collection empty?
        guard self.startIndex != self.endIndex else { return nil } 
        // Get the second index
        let index = self.index(after: self.startIndex)
        // Is that index valid?
        guard index != self.endIndex else { return nil } 
        // Return the second element
        return self[index]
    } 
}

使用切片更高效地获取第二个元素:

// 使用dropFirst获取去除第一个元素之后的切片,然后获取切片的第一个元素
var second: Element? {
    return self.dropFirst().first
}



切片会引用原始的集合,如果切片不释放引用,原始的集合依然存在于内存中:

extension Array {
    var firstHalf: ArraySlice<Element> {}
    return self.dropLast(self.count / 2)
}
var array = [1, 2, 3, 4, 5, 6, 7, 8]
var firstHalf = array.firstHalf // [1, 2, 3, 4]
array = []

print(firstHalf.first!) // 1

// 复制切片到另一个集合中,释放原始的集合
let copy = Array(firstHalf) // [1, 2, 3, 4]
firstHalf = []
print(copy.first!)



懒加载(lazy方法)



避免低效地去迭代集合中的所有值来获取部分值:

// 返回值为[Int]类型;
// 迭代所有的值,获取最后结果;
let items = (1...4000).map { $0 * 2 }.filter { $0 < 10 }
print(items.first) // 迭代1...4000后获取第一个值

// 返回值为LazyFilterCollection<LazyMapCollection<(ClosedRange<Int>), Int>>类型;
// 在需要获取部分值时,可以避免迭代所有的值;
// 但是每次从items获取值,都会进行新一次的计算;
let items = (1...4000).lazy.map { $0 * 2 }.filter { $0 < 10 } 
print(items.first) // 仅迭代一次



懒加载会重复计算,可以将结果保存到另一个集合中:

let bears = ["Grizzly", "Panda", "Spectacled", "Gummy Bears", "Chicago"]
let redundantBears = bears.lazy.filter {
    print("Checking '\($0)'")
    return $0.contains("Bear")
}

// 输出"Grizzly", "Panda", "Spectacled", "Gummy Bears"这三个元素
// 每次调用redundantBears.first都会输出这3个元素
print(redundantBears.first!)

// 将结果赋值给另一个集合,以防止每次获取过滤结果都进行计算
let filteredBears = Array(redundantBears)
print(filteredBears.first!)
何时使用lazy方法?



修改集合



接下来介绍另外两种集合协议:

Mutable Collection
// constant time
subscript(_: Self.Index) -> Element { get set }

Range Replaceable Collections
replaceSubrange(_:, with:)




访问集合时发生了崩溃?思考以下几个问题:



在数组中使用无效的索引,会发生崩溃:

var array = ["A", "B", "C", "D", "E"] 
let index = array.firstIndex(of: "E")! 
array.remove(at: array.startIndex) 

// index已失效
print(array[index]) // Fatal Error: Index out of range.

// 重新获取有效的索引可以避免这种问题发生
if let idx = array.firstIndex(of: "E") {
    print(array[idx]) 
}

在字典中依然要面对这种问题:

var favorites: [String : String] = [
    "dessert" : "honey ice cream",
    "sleep" : "hibernation",
    "food" : "salmon"
]

let foodIndex = favorites.index(forKey: "food")!
print(favorites[foodIndex]) // (key: "food", value: "salmon")

favorites["accessory"] = "tie"
favorites["hobby"] = "stealing picnic supplies"

print(favorites[foodIndex]) // Fatal error: Attempting to access Dictionary elements using an invalid Index

// 重新获取有效的索引可以避免这种问题发生
 if let foodIndex = favorites.index(forKey: "food") {
     print(favorites[foodIndex])
 }



使用索引和切片的建议:



你的集合可以多线程访问吗?



运行以下代码,会发生什么事?

var sleepingBears = [String]()
let queue = DispatchQueue.global() // 并发队列,会有多个线程
queue.async { sleepingBears.append("Grandpa") }
queue.async { sleepingBears.append("Cub") }

sleepingBears的值可能为 ["Grandpa", "Cub"], ["Cub", "Grandpa"], ["Grandpa"], ["Cub"]
甚至出现类似的错误:malloc: *** error for object 0x100586238: pointer being freed was not allocated

使用ThreadSanitizer(TSAN)进行检测,可以看到类似如下信息:

 
WARNING: ThreadSanitizer: Swift access race
Modifying access of Swift variable at 0x7b0800023cf0 by thread Thread 3:
...
 Previous modifying access of Swift variable at 0x7b0800023cf0 by thread Thread 2:
 ...
 Location is heap block of size 24 at 0x7b0800023ce0 allocated by main thread:
 ...
SUMMARY: ThreadSanitizer: Swift access race main.swift:515 in closure #1 in gotoSleep()



使用串行队列替换global()并发队列,即可使集合的操作在单个线程中进行。

var sleepingBears = [String]()
let queue = DispatchQueue(label: "Bear-Cave")  // 串行队列,单一线程调度
queue.async { sleepingBears.append("Grandpa") }
queue.async { sleepingBears.append("Cub") }
queue.async { print(sleepingBears) } //  ["Grandpa", "Cub"]



多线程操作集合的建议:



建议使用不可变的集合:



在使用集合时,提前预留空间,减少内存分配操作:
Array.reserveCapacity(_:)
Set(minimumCapacity:)
Dictionary(minimumCapacity:)


桥接(Bridging)



Foundation框架中的集合

Foundation框架中的集合都是引用类型,而Swift的集合都是值类型;
在操作Foundation框架中的集合时,要谨慎!

转换Objc中的类型为Swift类型时,桥接都会有消耗,虽然看似代价不大,但是如果桥接的量很大,这个过程就很值得关注了。



识别桥接问题:



观察以下代码:

let story = NSString(string: """
Once upon time there lived a family of Brown Bears. They had long brown hair.
...
They were happy with their new hair cuts. The end.
""")

let text = NSMutableAttributedString(string: story)

// text.string由NSString转为String,用Instruments检测到这个操作需要耗费大量时间
let range = text.string.range(of: "Brown")!  // Range<String.Index>
let nsrange = NSRange(range, in: text.string) // NSRange
text.addAttribute(.foregroundColor, value: NSColor.brown, range: nsrange)



桥接操作发生在哪里?

Bridging



更改耗时严重的桥接操作代码,优化性能:

let string = text.string as NSString // NSString
let nsrange = string.range(of: "Brown") // 依然有桥接操作发生,"Brown"由String转为NSString



何时使用Foundation框架中的集合?



接下来怎么办?




如有错误,欢迎指出!😀






参考内容:
Using Collections Effectively - Apple WWDC

上一篇 下一篇

猜你喜欢

热点阅读