题目
实现一个带缓存和加载状态指示的网络图片组件
信息
- 类型:问答
- 难度:⭐⭐
考点
异步数据加载, 状态管理, 缓存机制, 自定义视图封装
快速回答
实现网络图片组件的关键要点:
- 使用
URLSession异步加载图片数据 - 通过
@State管理加载状态(加载中/成功/失败) - 使用
NSCache实现内存缓存优化性能 - 提供占位符和错误状态UI
- 封装成可复用的
View组件
核心原理说明
在SwiftUI中实现网络图片加载需要解决三个核心问题:1) 异步数据获取不阻塞主线程 2) 图片缓存减少网络请求 3) 状态驱动UI更新。通过结合URLSession、NSCache和SwiftUI的状态管理机制,可以创建高性能的图片加载组件。
代码实现示例
class ImageCache {
static let shared = ImageCache()
private let cache = NSCache<NSString, UIImage>()
func get(forKey key: String) -> UIImage? {
return cache.object(forKey: key as NSString)
}
func set(_ image: UIImage, forKey key: String) {
cache.setObject(image, forKey: key as NSString)
}
}
struct NetworkImage: View {
let url: URL
@State private var image: UIImage?
@State private var isLoading = false
@State private var error: Error?
var body: some View {
Group {
if let image = image {
Image(uiImage: image)
.resizable()
} else if isLoading {
ProgressView()
} else if error != nil {
Image(systemName: "photo.on.rectangle")
.foregroundColor(.gray)
}
}
.task { await loadImage() }
}
private func loadImage() async {
// 检查缓存
if let cachedImage = ImageCache.shared.get(forKey: url.absoluteString) {
image = cachedImage
return
}
isLoading = true
defer { isLoading = false }
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let uiImage = UIImage(data: data) {
image = uiImage
ImageCache.shared.set(uiImage, forKey: url.absoluteString)
} else {
throw URLError(.badServerResponse)
}
} catch {
self.error = error
}
}
}
// 使用示例
struct ContentView: View {
var body: some View {
NetworkImage(url: URL(string: "https://example.com/image.jpg")!)
.frame(width: 200, height: 200)
}
}最佳实践
- 缓存策略:采用内存缓存(
NSCache)自动释放内存,可扩展为磁盘缓存 - 线程安全:使用
async/await保证线程正确性 - 生命周期管理:
.task修饰符自动取消未完成请求 - 错误处理:提供明确的错误状态UI
- 可扩展性:预留占位符自定义接口
常见错误
- 内存泄漏:未处理取消逻辑导致任务堆积
- 线程冲突:在非主线程更新UI状态
- 缓存失控:未设置缓存大小限制导致内存溢出
- 状态管理混乱:未区分加载中/成功/失败三种状态
- 网络请求未复用:相同URL重复发起请求
扩展知识
- 高级缓存策略:可结合
URLCache实现磁盘缓存 - 图片处理:添加渐进式加载/图片解码优化
- 替代方案:
AsyncImage(iOS15+)的基本用法:AsyncImage(url: url) { phase in switch phase { case .success(let image): image.resizable() case .failure: ErrorView() case .empty: ProgressView() @unknown default: EmptyView() } } - 性能优化:预加载机制、请求优先级设置
- 测试方案:Mock
URLProtocol进行单元测试