You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中MVVM结合Combine的数据传递实现困惑求助

SwiftUI MVVM 数据传递问题解答

问题背景

我是一名从Android转iOS开发数月的开发者,正在用SwiftUI开发类社交应用的信息流功能,对MVVM的实现机制存在困惑。当前我的View和ViewModel代码如下:

View代码

@ObservedObject private var viewModel = FeedViewModelImpl()
        
var body: some View {
    NavigationView {
        ScrollView {
            LazyVStack {
                let listOfPosts: [FeedItemEntity] = viewModel.getFeed()
                ForEach(listOfPosts) { post in
                    PostItemView(item: post)
                }
            }
        }
    }
}

ViewModel代码

final class FeedViewModelImpl: ObservableObject, FeedViewModel {

    @Published var feed: [FeedItemEntity]
    
    @Published var state: FeedState = .na
    @Published var hasError: Bool = false

    var feedUseCase: FeedUseCase
    
    private var subscriptions = Set<AnyCancellable>()

    init() {
        self.feedUseCase = FeedUseCaseImpl.shared
        setupErrorSubscription()
    }
    
    func getFeed() {
        feedUseCase
            .getFeed()
            .sink { res in
                switch res {
                case .failure(let err):
                    self.state = .failure(error: err)
                default:
                    break
                }
            } receiveValue: { [weak self] feedsValue in
                self?.feed = feedsValue
                self?.state = .success
            }
            .store(in: &subscriptions)
    }
    
}

我不清楚SwiftUI中ViewModel与View之间如何传递数据,Android里会在UI层构建observable循环等待事件触发,但SwiftUI里不知道怎么实现,求指导。


问题分析与解决方案

核心问题点

你当前的代码存在两个关键错误:

  1. 错误调用getFeed()获取数据:getFeed()是触发异步网络请求的方法,它不返回数据,而是请求完成后更新ViewModel的feed属性。你直接把它的返回值赋值给listOfPosts,完全不符合逻辑。
  2. 未正确利用SwiftUI的响应式机制:你没有通过观察ViewModel的@Published属性来驱动View刷新,而是试图用类似Android的手动方式处理数据传递。

SwiftUI MVVM数据传递逻辑(对比Android)

  • Android中:UI层订阅Observable对象,手动监听数据变化并更新UI。
  • SwiftUI中:
    • ViewModel用@Published标记需要被观察的属性(相当于Android的Observable)。
    • View用@StateObject(iOS14+推荐)或@ObservedObject持有ViewModel(相当于Android的订阅者)。
    • 当ViewModel的@Published属性发生变化时,SwiftUI会自动触发View的重绘,无需手动编写循环等待逻辑。

修正后的代码

1. ViewModel修正(补全初始化+优化逻辑)

final class FeedViewModelImpl: ObservableObject, FeedViewModel {

    // 初始化空数组,避免编译错误
    @Published var feed: [FeedItemEntity] = []
    
    @Published var state: FeedState = .na
    @Published var hasError: Bool = false

    var feedUseCase: FeedUseCase
    
    private var subscriptions = Set<AnyCancellable>()

    init(feedUseCase: FeedUseCase = FeedUseCaseImpl.shared) {
        self.feedUseCase = feedUseCase
        setupErrorSubscription()
    }
    
    func getFeed() {
        // 开始请求时更新状态为加载中
        state = .loading
        feedUseCase
            .getFeed()
            .sink { [weak self] res in
                guard let self = self else { return }
                switch res {
                case .failure(let err):
                    self.state = .failure(error: err)
                    self.hasError = true
                default:
                    break
                }
            } receiveValue: { [weak self] feedsValue in
                guard let self = self else { return }
                self.feed = feedsValue
                self.state = .success
                self.hasError = false
            }
            .store(in: &subscriptions)
    }
    
    // 补全setupErrorSubscription实现(示例)
    private func setupErrorSubscription() {
        $state
            .sink { state in
                if case .failure = state {
                    self.hasError = true
                }
            }
            .store(in: &subscriptions)
    }
    
}

// 假设FeedState枚举定义
enum FeedState {
    case na
    case loading
    case success
    case failure(error: Error)
}

2. View修正(利用响应式机制+处理状态)

// iOS14+推荐用@StateObject持有ViewModel,保证生命周期和View一致
@StateObject private var viewModel = FeedViewModelImpl()
        
var body: some View {
    NavigationView {
        ScrollView {
            LazyVStack {
                // 直接遍历viewModel.feed,feed变化时View自动刷新
                ForEach(viewModel.feed) { post in
                    PostItemView(item: post)
                }
            }
        }
        // 在View出现时触发数据请求
        .onAppear {
            viewModel.getFeed()
        }
        // 根据状态显示加载/错误提示
        .overlay {
            switch viewModel.state {
            case .loading:
                ProgressView("加载中...")
            case .failure(let error):
                Text("加载失败:\(error.localizedDescription)")
                    .foregroundColor(.red)
            default:
                EmptyView()
            }
        }
    }
}

关键说明

  • @StateObject vs @ObservedObject:如果ViewModel是当前View创建的,用@StateObject;如果是从父View传递过来的,用@ObservedObject。前者能保证ViewModel不会随View重绘被重复创建。
  • 自动刷新机制:当ViewModel的@Published属性(比如feed)被赋值时,SwiftUI会自动检测到变化,触发View的body重新计算,从而更新UI。
  • 状态处理:通过state属性可以处理加载、成功、错误等状态,在View中用overlay或条件渲染展示对应UI,提升用户体验。

内容的提问来源于stack exchange,提问作者Seb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 21:39:56