题目
实现高性能可过滤列表视图与自定义异步图片加载
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
SwiftUI性能优化,Combine框架应用,自定义视图与布局,状态管理,异步数据处理
快速回答
实现高性能SwiftUI列表需要:
- 使用
LazyVStack或List配合ForEach实现视图重用 - 通过
@ViewBuilder构建自定义单元格视图 - 采用
Combine的Debounce和RemoveDuplicates优化搜索过滤 - 使用
Task和@State管理异步图片加载状态 - 实现
Equatable协议减少无效刷新
问题场景
在SwiftUI中处理10000+条数据列表,要求:1) 实现实时搜索过滤 2) 自定义单元格布局 3) 异步加载网络图片 4) 滚动流畅不卡顿。
核心解决方案
1. 数据结构与状态管理
struct Product: Identifiable, Equatable {
let id: UUID
var name: String
var imageURL: URL?
// 实现Equatable减少无效刷新
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.id == rhs.id && lhs.name == rhs.name
}
}
class ProductViewModel: ObservableObject {
@Published var searchText = ""
@Published private(set) var filteredProducts: [Product] = []
private var allProducts: [Product] = []
private var cancellables = Set<AnyCancellable>()
init() {
// 模拟初始化10000条数据
allProducts = (0..<10000).map {
Product(id: UUID(), name: "Product \($0)", imageURL: URL(string: "https://picsum.photos/200?random=\($0)"))
}
// Combine处理搜索过滤(带防抖)
$searchText
.debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)
.removeDuplicates()
.map { [allProducts] text in
text.isEmpty ? allProducts : allProducts.filter { $0.name.contains(text) }
}
.assign(to: &$filteredProducts)
}
}2. 列表视图优化实现
struct ProductListView: View {
@StateObject var viewModel = ProductViewModel()
var body: some View {
VStack {
// 搜索框
TextField("Search...", text: $viewModel.searchText)
.padding()
// 使用LazyVStack优化性能
ScrollView {
LazyVStack(spacing: 0) {
ForEach(viewModel.filteredProducts) { product in
ProductRow(product: product)
.padding(.vertical, 8)
}
}
}
}
}
}
// 自定义单元格视图
struct ProductRow: View {
let product: Product
var body: some View {
HStack {
// 异步图片加载视图
AsyncImageView(url: product.imageURL)
.frame(width: 60, height: 60)
Text(product.name)
.font(.headline)
Spacer()
}
.padding(.horizontal)
}
}3. 异步图片加载实现
struct AsyncImageView: View {
let url: URL?
@State private var image: UIImage?
@State private var isLoading = false
var body: some View {
Group {
if let image = image {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
} else if isLoading {
ProgressView()
} else {
Image(systemName: "photo")
}
}
.task {
await loadImage()
}
}
private func loadImage() async {
guard let url = url, image == nil else { return }
isLoading = true
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let loadedImage = UIImage(data: data) {
// 主线程更新UI
await MainActor.run {
image = loadedImage
}
}
} catch {
print("Image loading failed: \(error)")
}
isLoading = false
}
}最佳实践
- 性能优化:优先使用
LazyVStack/LazyHStack替代List处理超大数据集 - 状态管理:
@StateObject管理ViewModel生命周期,@Published驱动UI更新 - 异步处理:使用
Task配合async/await管理异步操作,避免回调地狱 - 内存优化:图片加载实现内存缓存(可扩展为
NSCache)
常见错误
- 直接使用List+大量数据:未实现单元格复用导致内存暴涨
- 未防抖的实时搜索:每次输入都触发过滤导致性能卡顿
- 主线程加载图片:阻塞UI导致滚动卡顿
- 未实现Equatable:数据微变化时引发整个列表刷新
扩展知识
- 高级优化:实现
onAppear/onDisappear控制图片加载时机 - 依赖注入:将ImageLoader作为环境对象注入
- 测试策略:使用
XCTest测试ViewModel逻辑,ViewInspector测试UI状态 - 替代方案:对于超大数据集(10万+),考虑使用
CoreData或Realm的懒加载