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
相关产品推荐
相关产品推荐

