主要参考书:《Advanced Swift》- objc
内建集合类型
-
数组
-
数组是值语义
- 意味着当为由let 声明的数组调用append 时,编译将会不通过;
- 且把一个已经存在的数组赋值给另一个变量时,这个数组的内容将会被复制。不过好在Swift 标准库中的所有集合类型都采用“写时复制“「Copy-On-Write」技术,因此在新或旧变量被写入新值之前,二者在内存空间中共享唯一存储。
-
数组的常见方法
- 迭代数组 ——
for x in array
- 迭代除了第一个元素以外的数组其余部分 ——
for x in array.dropFirst()
- 迭代除了最后 5 个元素以外的数组 ——
for x in array.dropLast(5)
- 枚举数组中的元素和对应的下标 ——
for (num, element) in collection.enumerated()
- 寻找一个指定元素的位置 ——
if let idx = array.index { someMatchingLogic($0) }
- 对数组中的所有元素进行变形 ——
array.map { someTransformation($0) }
- 筛选出符合某个特定标准的元素 ——
array.filter { someCriteria($0) }
- 可见,Swift并不鼓励做索引计算,因为手动计算和使用索引将会带来很多潜在bug
- 虽然无效的下表操作会造成可控的崩溃,但从内存安全的角度来看,下标操作是绝对安全的,因为标准库中的集合总是会执行边界检查,并禁止越界索引对内存的访问
- 迭代数组 ——
-
数组变形
-
Map
// 求斐波那契数列前五项平方 var fibs:[Int] = {0,1,1,2,3,5} // 遍历式 var squared: [Int] = [] for fib in fibs{ squared.append(fib * fib) } squared //[0,1,1,4,9,25] // 函数式 let squares = fibs.map{fib in fib * fib} squares //[0,1,1,4,9,25] // 二者完全等价 // 但使用map 时,使用者有了更大的空间使得原本不得不被声明为var 的值改为let 声明 // 实现 extension Array{ func map<T>(_ transform:(Element) -> T) -> [T]{ var result: [T] = [] result.reverserCapacity(count) for x in self{ result.append(transform(x)) } return result } }
-
标准库中集合的其他常见方法
// map 和 flatMap — 对元素进行变换。 // filter — 只包含特定的元素。 // allSatisfy — 针对一个条件测试所有元素。 // ★reduce — 将元素聚合成一个值。 // forEach — 访问每个元素。 // sort(by:), sorted(by:), lexicographicallyPrecedes(_:by:), 和 partition(by:) — 重排元素。 // firstIndex(where:), lastIndex(where:), first(where:), last(where:), 和contains(where:) — 一个元素是否存在? // min(by:) 和 max(by:) — 找到所有元素中的最小或最大值。 // elementsEqual(_:by:) 和 starts(with:by:) — 将元素与另一个数组进行比较。 // split(whereSeparator:) — 把所有元素分成多个数组。 // prefix(while:) — 从头取元素直到条件不成立。 // drop(while:) — 当条件为真时,丢弃元素;一旦不为真,返回其余的元素 (和 prefix 类 似,不过返回相反的集合)。 // removeAll(where:) — 删除所有符合条件的元素。 // accumulate — 累加,和 reduce 类似,不过是将所有元素合并到一个数组中,并保留合并时每一步的值。 // count(where:) — 计算满足条件的元素的个数 (它应该是 Swift 5 标准库的一部分,但 由于和 count 属性的名字冲突而延迟了,它可能会在随后的版本中被重新引入)。 // indices(where:) — 返回一个包含满足某个条件的所有元素的索引列表,和 index(where:) 类似,但是不会在遇到首个元素时就停止。
-
filter:选出一个数组中满足条件的元素组成新数组
// 寻找0 - 10 以内所有数平方中的偶数项 (1..<10).map{$0 * $0}.filter{$0 % 2 == 0} // 实现 extension Array{ func filter(_ isIncluded:(Element) -> Bool -> [Element]){ var result: [Element] = [] for x in self where isIncluded(x){ result.append(x) } return result } }
-
reduce:将数组中的所有元素合并为一个新的值
let fibs = [0,1,1,2,3,5] let sum = fibs.reduce(0){total, num in total + num} // 12 // 运算符本质上也是函数,因此上述表达式与下述表达式功能一致 let sum = fibs.reduct(0, +) // reduce 的结果与运算过程不必拘泥于数据类型 let sum = fibs.reduce(str, num in str + "\(nums),") // 得到一个字符串 "0,1,1,2,3,5," // 实现 extension Array{ func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) -> Result) -> Result { var result = initialResult for x in self{ result = nextPartialResult(result, x) } return result } }
-
flatMap:一次性解决数组的提取和展平
// 读取一个Markdown 文件,返回包含该文件中所有链接的URL 数组 func extractLinks(markdownFile: String) -> [URL] let markdownFiles: [String] // 普通的通过map 实现,获得的是[[URL]] let nextedLinks = markdownFiles.map(extractLinks) // 将[[URL]] 展开,获得[URL] let links = nestedLinks.joined // 等价于直接的一个flatMap let links_0 = markdownFiles.flatMap(links) // 实现 extension Array{ func flatMap<T>(_ transform:(Element) -> [T]) -> [T]{ var result: [T] = [] for x in self{ result.append(contentsOf: transform(x)) } return result } } // 将不同数组里的元素进行合并 let suits = ["♠", "♥", "♣", "♦"] let ranks = ["J","Q","K","A"] let result = suits.flatMap{suit in ranks.map{rank in (suit, rank) } }/* [("♠", "J"), ("♠", "Q"), ("♠", "K"), ("♠", "A"), ("♥", "J"), ("♥","Q"), ("♥", "K"), ("♥", "A"), ("♣", "J"), ("♣", "Q"), ("♣", "K"), ("♣", "A"), ("♦", "J"), ("♦", "Q"), ("♦", "K"), ("♦", "A")] */
-
-
数组迭代 - forEach
for element in [1,2,3]{ print(element) } [1,2,3].forEach{element in print(element) } // for 循环和forEach 闭包并没有太大区别,但当需要在循环内对函数进行return 时,二者将有大不同 (1..<10).forEach{number in print(number) if(number > 2) {return} } // 该return 实际上并不会终止循环,它做的仅仅是从闭包中返回 // 因此在forEach 的实现中会开始下一个循环的迭代
- 当希望对数组中的每个元素调用某一函数时,通过结合forEach 传递函数名将使代码看上去更加简洁
-
数组切片
let slice = fibs[1..] //[1,1,2,3,5] type(of: slice) // ArraySlice<Int>
- 切片类型只是数组的一种表示方式,背后的数据仍然是原来的数组,只不过用ArraySlice 的方式来表示。因此数组的元素不会被复制,所以创建一个切片的代价非常小。
- ArraySlice 和Array 满足了相同的协议(最重要的是Collection协议),所以两者具有的方法一致,因此可以将切片当成数组来处理。
- 如果需要将切片转为数组,可以通过将其传递给数组的构建方法:
let new Array = Array(slice)
- ★ 谨记:切片和背后的数组通过相同的索引来引用元素,因此切片索引不需要从0 开始。
-
-
字典
-
基本特性
- 无序
- 返回的总是Optional 值,当指定的键不存在时,返回nil。在数组中若使用越界下标将导致程序崩溃。
-
基本方法
enum Setting{ case text(String) case int(Int) case bool(Bool) } let defaultSettings: [String: Setting] = [ "Airplane Mode": .bool(false) "Name": .text("My iPhone") ]
- 移除:
- 将对应键的值设为nil
- removeValue() 将会返回被移除的值
- 更新:
- updateValue
- 移除:
-
常见的其他方法
-
merge
var settings = defaultSettings let overriddenSettings: [String: Setting] = ["Name": .text("Jane's iPhone")] settings.merge(overriddenSettings, uniquingKeysWith:{$1}) settings //["Name": Setting.text("Jane\'s iPhone"), "Airplane Mode": Setting.bool(false)] //合并settings 和overrideenSettings,策略{$1}表示当某个键同时存在于settings 和overridenSettings 时,采用后者中的值
-
从一个字典构建另一个字典
// 对能保证键唯一性的使用Dictionary(uniqueKeysWithValues: ) // 对无法保证的使用Dictionary extension Sequence where Element: Hashable{ var frequencies: [Element: Int]{ let frequencyPairs = self.map{($0, 1)} // 从Dictionary 构建Dictionary return Dictionary(frequencyPairs, uniquingKeysWith: +) } } let frequencies = "hello".frequencies //["0":1, "h":1, "e":1, "l":2] frequencies.filter{$0.value > 1} //["l": 2]
-
对字典的值做映射
let settingsAsStrings = settings.mapValues { setting -> String in switch setting { case .text(let text): return text case .int(let number): return String(number) case .bool(let value): return String(value) } } settingsAsStrings // ["Name": "Jane\'s iPhone", "Airplane Mode": "false"]
-
-
Hashable
- 对于结构体和枚举,只要它们是由可哈希的类型组成的,那么Swift 就可以帮我们自动合成Hashable 协议所需要的实现。
- 如果不能利用自动Hashable 合成,那么需要首先让类型实现Equatable协议,然后可以实现
hash(into: )
方法来满足Hashable 协议。- 该方法接受一个Hasher 类型参数,这个类型封装了一个通用的哈希函数,并在使用者向其提供数据时捕获哈希函数状态。
-
应避免使用不具有值语义(如可变的对象)作为字典的键。当将一个对象用作字典键后,改变了它的内容,它的哈希值和/或相等特性往往也会改变。此时将无法在字典中再次找到它。此时字典会在错误的位置存储对象,这将导致字典内部存储的错误。
- 而对于具有值语义的键,因为键不会和复制的值公用存储,因此它不会被从外部改变,因此不存在这个问题
-
-
集合
-
基本特性
- 无序
- 元素需实现 Hashable
- 测试是否包含复杂度为常数
-
常用方法
let iPods: Set = ["iPod touch", "iPod nano", "iPod mini", "iPod shuffle", "iPod Classic"] let discontinuedIPods: Set = ["iPod mini", "iPod Classic", "iPod nano", "iPod shuffle"] // 补集 let currentIPods = iPods.subtracting(discontinuedIPods) // ["iPod touch"] // 交集 let touchscreen: Set = ["iPhone", "iPad", "iPod touch", "iPod nano"] let iPodsWithTouch = iPods.intersection(touchscreen) //["iPod nano", "iPod touch"] // 并集 var discontinued: Set = ["iBook", "Powerbook", "Power Mac"] discontinued.formUnion(discontinuedIPods) discontinued /* ["iPod shuffle", "iBook", "iPod Classic", "Powerbook", "iPod mini", "iPod nano", "Power Mac"] */
-
IndexSet:高效的索引存储集合
- 对于连续的索引,仅需存储开头和结尾两个值,而不需要存储所有备选索引。因此,虽然功能上等价于Set<Int>,但实际运行中较之更为高效
-
CharacterSet:搞笑的存储Unicode 编码点的集合。
- 经常被用来检查一个特定字符串是否只包含某个字符子集
-
在闭包中使用集合
// 筛选只出现过一次的元素 extension Sequence where Element: Hashable{ func unique() -> [Element]{ var seen: Set<Element> = [] return filter{element in if seen.contais(element){ return false }else{ seen.insert(element) return true } } } } [1,2,3,12,1,3,4,5,6,4,6].unique() //[1,2,3,12,4,5,6]
-
-
Range
-
a…b 表示两端闭
-
a…<b 表示左闭右开
-
a… 表示左闭右无穷
-
…b 表示左无穷右闭
-