数据结构

Array

关于Swift的数组,有三种不同的形式:

  • ContiguousArray: 其实我之前也一直没有注意这个形式的数组,因为平时更多在写Objc,很少写Swift。ContiguousArray就是强制规划一个连续的空间来储存Elements。当用ContiguousArray来储存对象数据(class@objc)时,其性能优于Array。
  • Array: 当Array储存值类型的数据的时候,内存空间是连续的。但是当储存类型是对象(class@objc)的时候,Array会自动桥接到NSArray上,内存可能不连续。其实可以看出,当Array和ContiguousArray都储存值类型(structenumeration)的话,他们的性能相当。
  • ArraySlice: 数组片段,它的作用就是让我们更快和更高效地对一个大数组的其中一部分最处理。ArraySlice是不会创建新的存储空间的,它和原本的Array在内存上共享同一区域。但是,这并不意味修改ArraySlice中的元素会影响到原来的Array。

更多:

ContiguousArray的创建:
1
2
var cArray = ContiguousArray<Any>(repeating: 0, count: 3)
print(type(of: cArray)) // "ContiguousArray<Any>\n"
Array的countcapacity

我们知道,当Array中的元素增加的时候,如果数组的内存不足,就需要新建一个更大的数组,然后从旧的数组中复制所有元素到新的数组里。现在的高级语言的Array都是Dynamic Array,你不需要自己去给数组扩容。为了提高数组扩的容的效率,底层代码都是成倍数的增加数组的大小。如Swift的Array的增长因子是2(阅读)。所以Capacity和Count是不同的,Capacity是指这个数组的总大小,Count是现在所有存在元素的个数。例子:

1
2
3
4
5
6
7
8
9
10
11
12
var cArray = ContiguousArray<Any>(repeating: 0, count: 3)
print(cArray.count) // 3
print(cArray.capacity) // 3

cArray.append(9)
print(cArray.count) // 4
print(cArray.capacity) // 6

// 当removeAll的时候,数组的所有空间都会被释放掉
cArray.removeAll()
print(cArray.count) // 0
print(cArray.capacity) // 0
Array的reserveCapacity(_:)

正如上面所说,当数组扩容的时候是有性能损耗的。当你大概知道需要多大内存容量的数组的时候,就可以用reserveCapacity(_:)来创建和保持数组的大小,省去数组Capacity变化带来的性能损耗。

ArraySlice的index并不总是开始于0:

ArraySlice会保留在原本数组中相同元素的index。

1
2
3
4
5
6
7
8
9
let absences = [0, 2, 0, 4, 0, 3, 1, 0]

let midpoint = absences.count / 2
//
let firstHalf = absences[..<midpoint]
let secondHalf = absences[midpoint...]

firstHalf[0] // 0
secondHalf[0] // Fatal error: Index out of bounds
ArraySlice会持有一个对原数组的强应用:

这意味着可能存在的内存溢出。在Swift文档中有如下建议:

Important: Long-term storage of ArraySlice instances is discouraged. A slice holds a reference to the entire storage of a larger array, not just to the portion it presents, even after the original array’s lifetime ends. Long-term storage of a slice may therefore prolong the lifetime of elements that are no longer otherwise accessible, which can appear to be memory and object leakage.


Set And Dictionary

Set和Dictionary在查找上都是O(1),这是源于他们都采用了hash。他们在存储时都是无序的。如果你有需求将一个自定义的类放入Set中或作为Dictionary的Key,那么这个类需要满足Hashable Protocal。

更多:

Dictionary的value可以为nil吗:

可以的。当你在创建一个dictionary的时候,如果value为nil,则这个字典中所有的value都会转化为optional。
另外,我们知道在Swift中去删除一个Key-Value pair,我们只需要给这个key所对应的value赋值nil。那么当value为nil时,这个方法会不会失效?答案是不会。
最后,当一个value是nil的时候,用if let判断时会怎样?答案是if let会通过,value为nil。例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var dic = ["a": 1,
"b": nil,
"c": 3]

var dicA = ["a": 1,
"b": 2,
"c": 3]

print(dic) // ["b": nil, "a": Optional(1), "c": Optional(3)]
print(dicA) // ["b": 2, "a": 1, "c": 3]

// if let
if let valueFromB = dic["b"] {
print("b has value: \(valueFromB)")
} else {
print("b has no value")
}

// 输出: b has value: nil

// 删除b
dic["b"] = nil
print(dic) // ["a": Optional(1), "c": Optional(3)]

String

对比与Objective-C,swift把很多类都变成了struct,字符串也不例外。这样的好处是增加了代码的安全,因为Swift为你最好了Copy-on-write,你也不用再像写ObjC那样要特别留意用copy关键字和动不动就要copy一下。当然,通过用inout关键字也是能实现传递引用的。

更多:

获得所有字符:

用一个Array来获得一个所有字符的数组。

1
2
let string = "abc"        // "abc"
let chars = Array(string) // ["a", "b", "c"]
subString:

String和Array很像,当你计算出一个range后,你就可以获得子字符串。

1
2
3
4
5
6
7
let fqdn = "useyourloaf.com"
let tldEndIndex = fqdn.endIndex
let tldStartIndex = fqdn.index(tldEndIndex, offsetBy: -3)
let range = Range(uncheckedBounds: (lower: tldStartIndex, upper: tldEndIndex))
fqdn[range] // "com"

From https://useyourloaf.com/blog/swift-string-cheat-sheet/”

Reference:
https://xiaozhuanlan.com/ios-interview 《iOS 面试之道》
https://useyourloaf.com
https://swiftdoc.org/