三种集合速览
类型 | 有序? | 唯一? | 键值对? | 适用场景举例 |
---|---|---|---|---|
Array | ✅ | ❌ | ❌ | 排行榜、聊天记录、播放队列 |
Set | ❌ | ✅ | ❌ | 去重标签、已读 ID 池、权限集合 |
Dictionary | ❌ | key 唯一 | ✅ | 缓存、JSON 解析、路由参数表 |
共同点
- 泛型化:存进去和取出来都是确定的类型,不会
as?
到处飞。 - 值语义:赋值即拷贝,修改副本不影响原值(写时优化,放心用)。
- 可变性由"声明为
var
/let
"决定,而不是像 Objective-C 那样区分NSMutable*
与不可变基类。
Array:有序可变列表
类型写法
swift
// 长写法
let a1: Array<Int> = [1, 2, 3]
// 简写,推荐
let a2: [Int] = [1, 2, 3]
创建空数组的两种姿势
swift
var numbers = [Int]() // 写法 1:初始化器
var digits: [Int] = [] // 写法 2:空字面量,更短
// 小技巧:如果上下文已知类型,可省 "= [Int]()",直接 [] 即可
带默认值批量创建
swift
// 生成 5 个 0.0 的 [Double]
let zeros = Array(repeating: 0.0, count: 5)
数组加法:拼接两个同类型数组
swift
let a = Array(repeating: 1, count: 3) // [1,1,1]
let b = Array(repeating: 2, count: 2) // [2,2]
let c = a + b // [1,1,1,2,2]
数组字面量初始化(最常用)
swift
// 类型推断
var shoppingList = ["Eggs", "Milk"]
// 显式标注
var shoppingList2: [String] = ["Eggs", "Milk"]
增删改查全套路
swift
var shoppingList = ["Eggs", "Milk"]
print(shoppingList)
shoppingList.append("Flour") // 尾插
print(shoppingList)
shoppingList += ["Baking Powder"] // 批量尾插
print(shoppingList)
shoppingList.insert("Maple Syrup", at: 0) // 头插
print(shoppingList)
let maple = shoppingList.remove(at: 0) // 按下标删
print(maple)
let last = shoppingList.removeLast() // 直接弹尾,省一次 count
print(last)
// 改
shoppingList[0] = "Six eggs"
print(shoppingList)
// 批量改(可改变长度)
shoppingList[1..<3] = ["Bananas", "Apples"] // 闭区间替换
print(shoppingList)
越界是运行时错误
Swift 的下标没有"返回 nil"版本,越界直接崩溃。
安全做法:
swift
if index >= 0 && index < shoppingList.count {
shoppingList[index] = "New"
}
// 或者封装一个 extension
extension Array {
subscript(safe index: Int) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
遍历
swift
// 只要元素
for item in shoppingList { }
// 既要索引又要元素
for (index, item) in shoppingList.enumerated() {
print("第 \(index + 1) 项:\(item)")
}
Set:唯一值的无序口袋
唯一性 + Hashable
Set 靠哈希表实现,元素必须遵循 Hashable
协议。
Swift 的 String
、Int
、Double
、Bool
以及无关联值的 enum 都默认实现。
创建与空 Set
swift
var letters = Set<Character>() // 空集合
letters = [] // 清空,但类型仍是 Set<Character>
字面量初始化
swift
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
// 类型可省,但 :Set 必须写,否则会被推断成 Array
增删查
swift
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
// 类型可省,但 :Set 必须写,否则会被推断成 Array
print(favoriteGenres.insert("Jazz")) // 返回 (inserted: true, memberAfterInsert: "Jazz")
if let removed = favoriteGenres.remove("Rock") {
print(removed)
} // 安全移除
let hasFunk = favoriteGenres.contains("Funk") // false
print(hasFunk)
集合运算:交、并、对称差、差
swift
let odd: Set = [1, 3, 5, 7, 9]
let even: Set = [0, 2, 4, 6, 8]
let prime: Set = [2, 3, 5, 7]
print(odd.union(even)) // 并
print(odd.intersection(even)) // 交 -> 空
print(odd.subtracting(prime)) // 差 -> [1,9]
print(odd.symmetricDifference(prime)) // 只在一边出现 -> [1,2,9]
关系判断:子集、超集、互斥
swift
let house: Set = ["🐶", "🐱"]
let farm: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
print(house.isSubset(of: farm)) // true
print(farm.isSuperset(of: house)) // true
print(farm.isDisjoint(with: ["🐦"])) // true,无交集
排序遍历
Set 本身无序,要顺序打印用 sorted()
:
swift
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
for g in favoriteGenres.sorted() {
print(g)
}
Dictionary:键值对的哈希表
类型写法
swift
// 长写法
let d1: Dictionary<String, Int> = [:]
// 简写,推荐
let d2: [String: Int] = [:]
空字典与清空
swift
var names: [Int: String] = [:]
names[16] = "sixteen"
names = [:] // 再次变空,类型仍在
字面量初始化
swift
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
// 类型推断为 [String: String]
读写与更新
swift
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
airports["LHR"] = "London" // 新增或覆盖
airports["LHR"] = "London Heathrow" // 覆盖
// 想拿到旧值再覆盖?用 updateValue
if let old = airports.updateValue("Dublin Airport", forKey: "DUB") {
print("旧值是 \(old)")
}
安全读取
下标返回的是 Value?
:
swift
if let name = airports["DUB"] {
print(name)
} else {
print("无此机场")
}
删除
swift
airports["APL"] = nil // 写法 1:赋 nil
if let removed = airports.removeValue(forKey: "DUB") { } // 写法 2:拿旧值
遍历 key/val/key-val
swift
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
for (code, name) in airports {
print("\(code): \(name)")
}
// 只要 key
for code in airports.keys {
print(code)
}
// 只要 value
for name in airports.values {
print(name)
}
// 转数组,方便 JSON 上报
let codeArray = [String](airports.keys)
print(codeArray)
顺序遍历
和 Set 一样,Dictionary 也不保证顺序:
swift
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
for code in airports.keys.sorted() {
print(code, airports[code]!)
}
实战小结:如何选型
- 需要"第 N 个"元素 ➜ Array
- 只关心"有没有",且要去重 ➜ Set
- 需要根据"某个 id 找实体" ➜ Dictionary
- 需要既有序又能去重?➜ 用
Array
+Set
双持:- Set 负责"存在性"去重;
- Array 负责顺序与下标。
例:
swift
struct UniqueArray<T: Hashable> {
private var array: [T] = []
private var set: Set<T> = []
mutating func append(_ new: T) -> Bool {
guard !set.contains(new) else { return false }
array.append(new)
set.insert(new)
return true
}
}
容易踩的 5 个小坑
let arr = [1,2,3]; arr.append(4)
➜ 编译错误:常量不可变。Set
的元素千万别用"自定义类"却忘了实现Hashable
,否则编译器直接罢工。- Array 批量替换区间时,新数组长度可以不同,但别拿
NSRange
混用。 - Dictionary 的
updateValue
返回的是旧值,而不是新值,别写反了。 - 遍历字典
keys
时强制解包!
是安全的,但前提是"遍历期间不修改字典",否则可能崩溃。
扩展场景:集合在真实项目里的 3 个用法
-
聊天已读池
服务端下发"已读消息 id 列表" → 客户端用
Set<Int64>
保存;本地新增一条消息时,
if readIDs.contains(msg.id)
决定"是否显示已读角标"。优势:O(1) 查询,内存占用远低于数组。
-
标签去重与快速合并
用户选择兴趣标签,多端同步。
本地用
Set<String>
存,同步时直接local.formUnion(remote)
完成合并,天然去重。 -
路由参数表
路由 URL 解析成
[String: String]
,中间件按 key 读取参数;因 Dictionary 的哈希查找复杂度接近 O(1),万级路由也不惧。
总结
- Array = 有序 + 可重;Set = 无序 + 唯一;Dictionary = 键值 + 哈希。
- 可变性由
var / let
决定,不再需要NSMutable*
那一套。 - 所有集合都是值语义,多线程场景下"拷贝即安全",但大集合要注意写时复制的性能。
- 遍历、区间替换、集合运算等 API 在 Swift 标准库里已高度封装,优先使用而非自己造轮子。