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

Combine框架下:ViewModel逻辑迁移至Service的@Published连接问题

问题解决:Combine框架下ViewModel与Service层@Published属性连接失败

问题根源

调用weatherService.getWeather()后立即赋值self.currentWeather = self.weatherService.weather时,网络请求是异步操作,此时weatherService.weather还未被赋值(仍为nil);同时未订阅WeatherService中@Published var weather的变化,导致后续weather更新时ViewModel无法感知。

方案一:让WeatherService返回Publisher(推荐)

这种方式贴合Combine响应式设计,Service负责提供数据流,ViewModel订阅后处理结果:

修改WeatherService

import Combine

final class WeatherService {
    public func getWeather(latitude: Double, longitude: Double) -> AnyPublisher<WeatherResponseModel, Error> {
        guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?lat=\(latitude)&lon=\(longitude)&appid=299279dbd985cab3b71f59d2c2593766") else {
            return Fail(error: NetworkingError.unknown).eraseToAnyPublisher()
        }
        
        return NetworkingManager.download(url: url)
            .decode(type: WeatherResponseModel.self, decoder: JSONDecoder())
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
}

修改WeatherViewModel

import Combine
import CoreLocation

final class WeatherViewModel: ObservableObject {
    @Published var currentWeather: WeatherResponseModel?
    private var cancellables = Set<AnyCancellable>()
    private let locationManager = LocationManager()
    private let weatherService = WeatherService()

    func fetchLocation() {
        locationManager.requestWhenInUseAuthorization()
            .flatMap { self.locationManager.requestLocation() }
            .flatMap { location -> AnyPublisher<WeatherResponseModel, Error> in
                self.weatherService.getWeather(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
            }
            .sink(receiveCompletion: { completion in
                NetworkingManager.handleCompletion(completion: completion)
            }, receiveValue: { [weak self] weather in
                self?.currentWeather = weather
            })
            .store(in: &cancellables)
    }
    
    // 移除原有的getCurrentWeather方法
}

方案二:订阅WeatherService的@Published属性

若希望保留WeatherService中的@Published var weather,可在ViewModel中订阅该属性变化:

修改WeatherViewModel

import Combine
import CoreLocation

final class WeatherViewModel: ObservableObject {
    @Published var currentWeather: WeatherResponseModel?
    private var cancellables = Set<AnyCancellable>()
    private let locationManager = LocationManager()
    private let weatherService = WeatherService()

    init() {
        // 订阅WeatherService的weather变化
        weatherService.$weather
            .receive(on: DispatchQueue.main)
            .sink { [weak self] weather in
                self?.currentWeather = weather
            }
            .store(in: &cancellables)
    }

    func fetchLocation() {
        locationManager.requestWhenInUseAuthorization()
            .flatMap { self.locationManager.requestLocation() }
            .sink { _ in
            } receiveValue: { location in
                self.weatherService.getWeather(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
            }
            .store(in: &cancellables)
    }
    
    // 移除原有的getCurrentWeather方法
}

注意事项

  • 方案一的Publisher返回方式更灵活,ViewModel可按需组合数据流,同时避免Service内部维护订阅的复杂性。
  • 两种方案均需使用[weak self]避免循环引用,防止内存泄漏。
  • 确保Info.plist中已添加Privacy - Location When In Use Usage Description权限描述。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 01:35:26