在iOS开发中,集合类型是构建应用的基础数据结构。Swift提供了三种主要的集合类型:数组(Array)、集合(Set)和字典(Dictionary)。每种类型都有其独特的特性和适用场景。本文将深入探讨这些集合类型的内部实现、性能特点、常用操作以及在实际项目中的应用技巧。
一、数组(Array)
数组是有序的元素集合,通过索引访问元素。在Swift中,数组是值类型,这意味着当你将一个数组赋值给另一个变量时,会创建一个副本。
1.1 数组的创建与初始化
// 空数组
var emptyArray: [Int] = []
var emptyArray2 = [Int]()
// 通过字面量创建
var names = ["Alice", "Bob", "Charlie"]
// 创建指定大小的数组
var repeatedArray = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0]
// 从其他集合创建
let set: Set = [1, 2, 3]
let arrayFromSet = Array(set) // 顺序不确定
1.2 数组的常用操作
// 增加元素
names.append("David") // 在末尾添加
names.insert("Eve", at: 1) // 在指定位置插入
// 删除元素
let removed = names.remove(at: 0) // 删除并返回
names.removeAll() // 清空数组
// 修改元素
names[1] = "Bob2"
// 访问元素
let first = names.first // 可选值
let last = names.last // 可选值
let element = names[2] // 直接访问
// 检查数组状态
let isEmpty = names.isEmpty
let count = names.count
1.3 数组的高级操作
// 过滤
let numbers = [1, 2, 3, 4, 5, 6]
let evenNumbers = numbers.filter { $0 % 2 == 0 } // [2, 4, 6]
// 映射
let squared = numbers.map { $0 * $0 } // [1, 4, 9, 16, 25, 36]
// 归约
let sum = numbers.reduce(0, +) // 21
// 排序
let sorted = numbers.sorted() // 升序
let sortedDesc = numbers.sorted(by: >) // 降序
// 分组
let grouped = Dictionary(grouping: names) { $0.first! } // 按首字母分组
1.4 数组的性能特点
- 随机访问:O(1)时间复杂度
- 插入/删除:在末尾操作O(1),在中间操作O(n)
- 内存:连续内存存储,缓存友好
1.5 实战应用:图片缓存管理器
class ImageCacheManager {
private var cache: [String: UIImage] = [:]
private var accessOrder: [String] = [] // LRU策略
func getImage(forKey key: String) -> UIImage? {
if let image = cache[key] {
// 更新访问顺序
if let index = accessOrder.firstIndex(of: key) {
accessOrder.remove(at: index)
}
accessOrder.append(key)
return image
}
return nil
}
func setImage(_ image: UIImage, forKey key: String) {
cache[key] = image
accessOrder.append(key)
// 如果缓存超过限制,移除最久未使用的
if accessOrder.count > 100 {
if let oldestKey = accessOrder.first {
cache.removeValue(forKey: oldestKey)
accessOrder.removeFirst()
}
}
}
}
二、集合(Set)
集合是无序的唯一元素集合。在Swift中,集合也是值类型,但要求元素必须是可哈希的(遵循Hashable协议)。
2.1 集合的创建与初始化
// 空集合
var emptySet: Set<Int> = []
var emptySet2 = Set<Int>()
// 通过字面量创建
var numbersSet: Set = [1, 2, 3, 4, 5]
// 从数组创建
let array = [1, 2, 3, 2, 1]
let setFromArray = Set(array) // [1, 2, 3]
2.2 集合的常用操作
// 添加元素
numbersSet.insert(6)
// 删除元素
numbersSet.remove(3)
// 检查包含
let contains = numbersSet.contains(2) // true
// 集合运算
let setA: Set = [1, 2, 3]
let setB: Set = [3, 4, 5]
let union = setA.union(setB) // [1, 2, 3, 4, 5]
let intersection = setA.intersection(setB) // [3]
let difference = setA.subtracting(setB) // [1, 2]
let symmetricDifference = setA.symmetricDifference(setB) // [1, 2, 4, 5]
// 检查关系
let isSubset = setA.isSubset(of: setB) // false
let isSuperset = setA.isSuperset(of: setB) // false
let isDisjoint = setA.isDisjoint(with: setB) // false
2.3 集合的性能特点
- 插入/删除/查找:平均O(1)时间复杂度
- 内存:哈希表实现,内存占用比数组大
- 无序:不能通过索引访问
2.4 实战应用:去重与关系管理
// 用户标签去重
class TagManager {
private var userTags: Set<String> = []
func addTag(_ tag: String) {
userTags.insert(tag)
}
func removeTag(_ tag: String) {
userTags.remove(tag)
}
func getCommonTags(with other: Set<String>) -> Set<String> {
return userTags.intersection(other)
}
}
// 权限管理
class PermissionManager {
private var userPermissions: Set<String> = []
func grantPermission(_ permission: String) {
userPermissions.insert(permission)
}
func hasPermission(_ permission: String) -> Bool {
return userPermissions.contains(permission)
}
func grantAllPermissions(_ permissions: [String]) {
userPermissions.formUnion(permissions)
}
}
三、字典(Dictionary)
字典是键值对的无序集合,通过键来访问值。在Swift中,字典也是值类型,键必须是可哈希的。
3.1 字典的创建与初始化
// 空字典
var emptyDict: [String: Int] = [:]
var emptyDict2 = [String: Int]()
// 通过字面量创建
var scores = ["Alice": 90, "Bob": 85, "Charlie": 88]
// 从元组数组创建
let pairs = [("Alice", 90), ("Bob", 85)]
var dictFromPairs = Dictionary(uniqueKeysWithValues: pairs)
// 分组创建
let names = ["Alice", "Bob", "Alice", "Charlie"]
var grouped = Dictionary(grouping: names) { $0 } // ["Alice": ["Alice", "Alice"], "Bob": ["Bob"], "Charlie": ["Charlie"]]
3.2 字典的常用操作
// 访问值
let aliceScore = scores["Alice"] // 可选值
let bobScore = scores["Bob", default: 0] // 提供默认值
// 添加/更新
scores["David"] = 92 // 添加
scores["Alice"] = 95 // 更新
// 删除
scores.removeValue(forKey: "Bob")
// 检查状态
let isEmpty = scores.isEmpty
let count = scores.count
// 遍历
for (name, score) in scores {
print("\(name): \(score)")
}
// 键和值的集合
let keys = Set(scores.keys) // 键的集合
let values = Array(scores.values) // 值的数组
3.3 字典的性能特点
- 查找/插入/删除:平均O(1)时间复杂度
- 内存:哈希表实现,内存占用较大
- 无序:不能通过索引访问
3.4 实战应用:配置管理器
class ConfigurationManager {
private var configurations: [String: Any] = [:]
// 泛型方法获取配置
func getConfiguration<T>(for key: String, default value: T) -> T {
if let config = configurations[key] as? T {
return config
}
return value
}
// 设置配置
func setConfiguration<T>(_ value: T, for key: String) {
configurations[key] = value
}
// 批量加载配置
func loadConfigurations(from dictionary: [String: Any]) {
configurations.merge(dictionary) { (_, new) in new }
}
// 获取所有配置
func getAllConfigurations() -> [String: Any] {
return configurations
}
}
// 使用示例
let configManager = ConfigurationManager()
configManager.setConfiguration("https://api.example.com", for: "apiBaseURL")
configManager.setConfiguration(30, for: "timeoutSeconds")
configManager.setConfiguration(true, for: "enableLogging")
let baseURL = configManager.getConfiguration(for: "apiBaseURL", default: "http://localhost")
let timeout = configManager.getConfiguration(for: "timeoutSeconds", default: 10)
四、集合类型的高级特性
4.1 可哈希性(Hashable)
所有集合类型的元素(或键)必须遵循Hashable协议。对于自定义类型,需要实现Hashable。
struct User: Hashable {
let id: Int
let name: String
// 实现Hashable需要实现hash(into:)方法
func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(name)
}
// 实现Equatable(Hashable的父协议)
static func == (lhs: User, rhs: User) -> Bool {
return lhs.id == rhs.id && lhs.name == rhs.name
}
}
// 使用自定义类型作为集合元素
var userSet: Set<User> = []
var userDict: [User: String] = [:]
4.2 集合的批量操作
// 批量添加
var numbers = [1, 2, 3]
numbers.append(contentsOf: [4, 5, 6])
// 批量删除
numbers.removeAll { $0 % 2 == 0 } // 删除所有偶数
// 批量替换
numbers.replaceSubrange(1...2, with: [10, 11, 12])
// 批量排序
numbers.sort { $0 > $1 } // 原地排序
4.3 集合的转换
// 数组转集合(去重)
let array = [1, 2, 3, 2, 1]
let set = Set(array) // [1, 2, 3]
// 集合转数组(随机顺序)
let set: Set = [1, 2, 3]
let array = Array(set) // 顺序不确定
// 字典转数组
let dict = ["Alice": 90, "Bob": 85]
let arrayFromDict = dict.map { "\($0.key): \($0.value)" } // ["Alice: 90", "Bob: 85"]
五、实战项目:联系人管理系统
下面是一个完整的实战示例,展示如何综合使用三种集合类型构建一个联系人管理系统。
// MARK: - 数据模型
struct Contact: Hashable {
let id: UUID
let name: String
let phone: String
let email: String
let tags: Set<String> // 使用集合存储标签
init(name: String, phone: String, email: String, tags: Set<String> = []) {
self.id = UUID()
self.name = name
self.phone = phone
self.email = email
self.tags = tags
}
}
// MARK: - 联系人管理器
class ContactManager {
// 使用字典存储联系人,键为ID,值为联系人对象
private var contacts: [UUID: Contact] = [:]
// 使用数组维护联系人列表(按姓名排序)
private var sortedContacts: [Contact] = []
// 使用集合存储所有标签
private var allTags: Set<String> = []
// 添加联系人
func addContact(_ contact: Contact) {
contacts[contact.id] = contact
sortedContacts.append(contact)
sortedContacts.sort { $0.name < $1.name }
allTags.formUnion(contact.tags)
}
// 删除联系人
func removeContact(id: UUID) {
if let contact = contacts[id] {
contacts.removeValue(forKey: id)
if let index = sortedContacts.firstIndex(of: contact) {
sortedContacts.remove(at: index)
}
// 重新计算所有标签
allTags = Set(contacts.values.flatMap { $0.tags })
}
}
// 按标签搜索
func searchByTag(_ tag: String) -> [Contact] {
return sortedContacts.filter { $0.tags.contains(tag) }
}
// 按姓名搜索
func searchByName(_ name: String) -> [Contact] {
return sortedContacts.filter { $0.name.lowercased().contains(name.lowercased()) }
}
// 获取所有标签
func getAllTags() -> [String] {
return Array(allTags).sorted()
}
// 获取联系人数量
func getContactCount() -> Int {
return contacts.count
}
// 批量导入联系人
func importContacts(_ contacts: [Contact]) {
for contact in contacts {
addContact(contact)
}
}
}
// MARK: - 使用示例
let manager = ContactManager()
// 创建联系人
let contact1 = Contact(name: "Alice", phone: "123-456-7890", email: "alice@example.com", tags: ["work", "friend"])
let contact2 = Contact(name: "Bob", phone: "234-567-8901", email: "bob@example.com", tags: ["work", "colleague"])
let contact3 = Contact(name: "Charlie", phone: "345-678-9012", email: "charlie@example.com", tags: ["family"])
// 添加联系人
manager.addContact(contact1)
manager.addContact(contact2)
manager.addContact(contact3)
// 搜索
let workContacts = manager.searchByTag("work") // Alice和Bob
let aliceContacts = manager.searchByName("Alice") // Alice
// 获取所有标签
let allTags = manager.getAllTags() // ["colleague", "family", "friend", "work"]
// 删除联系人
manager.removeContact(id: contact2.id)
// 打印结果
print("总联系人数: \(manager.getContactCount())") // 2
print("工作联系人: \(workContacts.count)") // 1(Bob已删除)
六、性能优化建议
6.1 选择合适的集合类型
| 操作 | 数组 | 集合 | 字典 |
|---|---|---|---|
| 随机访问 | ✅ O(1) | ❌ | ❌ |
| 查找元素 | ❌ O(n) | ✅ O(1) | ✅ O(1) |
| 插入/删除(末尾) | ✅ O(1) | ✅ O(1) | ✅ O(1) |
| 插入/删除(中间) | ❌ O(n) | ✅ O(1) | ✅ O(1) |
| 去重 | ❌ | ✅ | ✅(键唯一) |
| 有序 | ✅ | ❌ | ❌ |
6.2 内存管理
// 避免不必要的复制
class LargeDataProcessor {
private var largeArray: [Data] = []
// 使用inout参数避免复制
func process(data: inout [Data]) {
// 处理数据
data.append(Data())
}
// 使用引用类型存储大型集合
private var sharedArray: [Data] = []
func getSharedArray() -> [Data] {
return sharedArray // 返回副本
}
func getSharedArrayReference() -> [Data] {
return sharedArray // 返回副本(值类型)
}
}
// 对于大型数据,考虑使用类而不是结构体
class LargeDataContainer {
var data: [Data] = []
}
6.3 并发访问
import Foundation
class ThreadSafeCollectionManager {
private let queue = DispatchQueue(label: "com.example.collection", attributes: .concurrent)
private var _contacts: [UUID: Contact] = [:]
// 使用读写锁
var contacts: [UUID: Contact] {
get {
return queue.sync { _contacts }
}
set {
queue.async(flags: .barrier) {
self._contacts = newValue
}
}
}
// 使用actor(Swift 5.5+)
actor ContactActor {
private var contacts: [UUID: Contact] = [:]
func addContact(_ contact: Contact) {
contacts[contact.id] = contact
}
func getContact(id: UUID) -> Contact? {
return contacts[id]
}
}
}
七、常见问题与解决方案
7.1 数组越界问题
// 错误示例
let array = [1, 2, 3]
// let value = array[3] // 运行时崩溃
// 正确做法
if array.indices.contains(3) {
let value = array[3]
}
// 或者使用安全访问
let value = array[safe: 3] // 自定义扩展
// 扩展Array添加安全访问
extension Array {
subscript(safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
7.2 字典键的哈希冲突
// 自定义类型的哈希实现
struct Product: Hashable {
let id: Int
let name: String
let price: Double
func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(name)
hasher.combine(price)
}
static func == (lhs: Product, rhs: Product) -> Bool {
return lhs.id == rhs.id && lhs.name == rhs.name && lhs.price == rhs.price
}
}
// 使用示例
var productDict: [Product: String] = [:]
let product1 = Product(id: 1, name: "iPhone", price: 999.99)
let product2 = Product(id: 1, name: "iPhone", price: 999.99)
productDict[product1] = "Apple"
productDict[product2] = "Apple" // 覆盖前一个值
7.3 集合的性能瓶颈
// 避免在循环中频繁修改集合
var numbers = [1, 2, 3, 4, 5]
// 低效做法
for number in numbers {
if number % 2 == 0 {
numbers.append(number * 2) // 在循环中修改数组
}
}
// 高效做法
let evenNumbers = numbers.filter { $0 % 2 == 0 }
let doubled = evenNumbers.map { $0 * 2 }
numbers.append(contentsOf: doubled)
// 或者使用临时数组
var tempNumbers = numbers
for number in numbers {
if number % 2 == 0 {
tempNumbers.append(number * 2)
}
}
numbers = tempNumbers
八、总结
在iOS开发中,正确选择和使用集合类型对应用性能和代码质量至关重要:
- 数组:适合需要保持顺序、频繁随机访问的场景
- 集合:适合需要去重、快速查找、集合运算的场景
- 字典:适合键值对映射、快速查找的场景
在实际项目中,经常需要组合使用这些集合类型,如联系人管理系统示例所示。理解每种类型的性能特点和适用场景,能够帮助开发者编写更高效、更健壮的代码。
记住,Swift的集合类型都是值类型,这意味着它们在赋值和传递时会创建副本。对于大型数据集,需要考虑内存使用和性能影响。在需要共享数据或处理大型数据时,可以考虑使用类或actor来管理数据。
通过合理选择集合类型、优化数据结构和算法,可以显著提升iOS应用的性能和用户体验。
