SwiftUI中@Published属性包装器的性能开销多大?有没有更优方案
@Published 开销说明
@Published 本身的运行时开销极低,本质只是在属性的willSet阶段触发ObservableObject的objectWillChange发布者信号,单次触发开销为纳秒级,对于你这种单次加载静态头像的场景,完全不需要担心这部分的性能损耗。你现有实现的主要性能浪费来自两个问题:
- 用
@ObservedObject托管ImageFetcher实例,每次父视图重绘触发ImageView初始化时,都会创建新的ImageFetcher实例,已经发起的请求会被废弃,还会重复发起网络请求 - 没有图片缓存机制,同一个头像每次进入页面都会重新请求
优化方案
按优先级从高到低排列:
1 修复现有实现的实例重复创建问题
把@ObservedObject换成@StateObject,让SwiftUI托管ImageFetcher的生命周期,保证实例只会创建一次,避免重复请求:
struct ImageView: View { @StateObject var fetcher: ImageFetcher init(_ firebaseManager: FirebaseManager) { self._fetcher = StateObject(wrappedValue: ImageFetcher(firebaseManager)) } // body 代码保持不变 }
2 完全抛弃ObservableObject,使用更轻量的实现
对于单次异步加载的静态内容,不需要额外定义ObservableObject类,直接用@State托管加载状态和结果即可,没有发布者监听的额外开销:
// 全局图片缓存,避免重复请求 class GlobalImageCache: NSCache<NSString, UIImage> { static let shared = GlobalImageCache() private override init() { super.init() countLimit = 100 // 最多缓存100张头像 } } struct ImageView: View { let firebaseManager: FirebaseManager @State private var image: UIImage? func fetchImage(id: String) async { // 先查缓存,命中直接返回 if let cachedImage = GlobalImageCache.shared.object(forKey: id as NSString) { image = cachedImage return } let ref = firebaseManager.STORAGE.reference().child(id) do { let data = try await ref.getData(maxSize: 2051240) guard let uiImage = UIImage(data: data) else { return } GlobalImageCache.shared.setObject(uiImage, forKey: id as NSString) image = uiImage } catch { print("加载失败: \(error.localizedDescription)") } } var body: some View { Button { Task { await fetchImage(id: "QDhKhNe5VnWDCORoQScIVVzcpzv2") } } label: { Text("Load Image") } if let image = image { Image(uiImage: image) .resizable() .frame(width: 50, height: 50) } else { Text("Loading...") } } }
3 优先使用系统原生AsyncImage(iOS 15+)
如果你的项目最低支持iOS15,直接使用系统提供的AsyncImage即可,内部自动处理加载状态、缓存、生命周期管理,代码最精简,开销最低:
struct ImageView: View { let firebaseManager: FirebaseManager @State private var imageURL: URL? func getDownloadURL(uid: String) async { let ref = firebaseManager.STORAGE.reference().child(uid) imageURL = try? await ref.downloadURL() } var body: some View { Button { Task { await getDownloadURL(uid: "QDhKhNe5VnWDCORoQScIVVzcpzv2") } } label: { Text("Load Image") } if let url = imageURL { AsyncImage(url: url) { phase in switch phase { case .empty: Text("Loading...") case .success(let image): image .resizable() .frame(width: 50, height: 50) case .failure: Text("加载失败") @unknown default: EmptyView() } } } else { Text("点击加载图片") } } }
内容的提问来源于stack exchange,提问作者John Sorensen
相关产品推荐
相关产品推荐

