如何在两个SwiftUI ViewModel间共享fetchPhoto函数?
不用复制代码的解决方案
你完全不需要把fetchPhoto()复制到两个ViewModel里,下面几种方法能帮你复用逻辑,同时保留现有两个ViewModel的结构:
1. 把图片处理逻辑抽成独立服务
核心思路是把获取图片、更新城市数组的逻辑单独拎出来做成一个服务类,两个ViewModel都调用这个服务就行。
实现步骤:
- 新建一个
CityPhotoService类,封装API调用和数组更新逻辑:
class CityPhotoService { private let cityService: CityServiceProtocol init(cityService: CityServiceProtocol) { self.cityService = cityService } // 通用方法,支持传入任意City类型的可变数组 func fetchAndUpdatePhoto(cityId: Int, in cities: inout [City]) async { do { let data = try await cityService.getPhoto(cityId: cityId) guard let photoUrl = data.data?.photoUrl else { return } if let index = cities.firstIndex(where: { $0.id == cityId }) { cities[index].photoUrl = photoUrl } } catch { print("获取城市图片失败:\(error.localizedDescription)") } } }
- 修改两个ViewModel,注入这个服务并调用方法:
CityViewModel修改后:
class CityViewModel: ObservableObject { let cityService: CityServiceProtocol private let cityPhotoService: CityPhotoService @Published var cities: [City] = [] init(cityService: CityServiceProtocol) { self.cityService = cityService self.cityPhotoService = CityPhotoService(cityService: cityService) } func fetchPhoto(cityId: Int) async { await cityPhotoService.fetchAndUpdatePhoto(cityId: cityId, in: &cities) } }
ProfileViewModel修改后:
class ProfileViewModel: ObservableObject { let profileService: ProfileServiceProtocol private let cityPhotoService: CityPhotoService @Published var savedCities: [City] = [] init(profileService: ProfileServiceProtocol, cityService: CityServiceProtocol) { self.profileService = profileService self.cityPhotoService = CityPhotoService(cityService: cityService) } func fetchSavedCityPhoto(cityId: Int) async { await cityPhotoService.fetchAndUpdatePhoto(cityId: cityId, in: &savedCities) } }
2. 用协议+扩展实现默认逻辑
定义一个协议,声明必要的属性和方法,然后通过协议扩展写好默认实现,让两个ViewModel遵守这个协议就能直接用方法。
实现步骤:
- 定义协议:
protocol CityPhotoFetching: ObservableObject { var cityService: CityServiceProtocol { get } var cities: [City] { get set } func fetchPhoto(cityId: Int) async }
- 扩展协议,写好
fetchPhoto的默认实现:
extension CityPhotoFetching { func fetchPhoto(cityId: Int) async { do { let data = try await cityService.getPhoto(cityId: cityId) guard let photoUrl = data.data?.photoUrl else { return } if let index = cities.firstIndex(where: { $0.id == cityId }) { cities[index].photoUrl = photoUrl } } catch { print("获取城市图片失败:\(error.localizedDescription)") } } }
- 让两个ViewModel遵守协议:
CityViewModel直接遵守:
class CityViewModel: ObservableObject, CityPhotoFetching { let cityService: CityServiceProtocol @Published var cities: [City] = [] init(cityService: CityServiceProtocol) { self.cityService = cityService } }
ProfileViewModel需要映射一下cities到savedCities:
class ProfileViewModel: ObservableObject, CityPhotoFetching { let profileService: ProfileServiceProtocol let cityService: CityServiceProtocol @Published var savedCities: [City] = [] // 协议要求的cities属性,映射到savedCities var cities: [City] { get { savedCities } set { savedCities = newValue } } init(profileService: ProfileServiceProtocol, cityService: CityServiceProtocol) { self.profileService = profileService self.cityService = cityService } }
3. 让CityView自己触发图片获取
把图片获取的逻辑从ViewModel移到视图层,让视图接收一个闭包参数,不同页面传入对应ViewModel的方法就行。
修改CityView:
struct CityView: View { @Binding var city: City // 接收一个获取图片的闭包 let fetchPhoto: (Int) async -> Void var body: some View { NavigationLink(destination: CityDetailView(cityId: city.id)) { VStack { KFImage(URL(string: city.photoUrl ?? "")) .resizable() .background(Color.white) .onAppear { Task { // 图片URL为空时才调用接口 if city.photoUrl == nil { await fetchPhoto(city.id) } } } Text(city.name) } } } }
- 在随机城市列表页面使用:
ForEach($viewModel.cities) { $city in CityView(city: $city, fetchPhoto: viewModel.fetchPhoto) }
- 在个人资料页面使用:
ForEach($viewModel.savedCities) { $city in CityView(city: $city, fetchPhoto: viewModel.fetchSavedCityPhoto) }
这里的fetchSavedCityPhoto就是ProfileViewModel里调用共用服务的方法(参考第一种方案)。
内容的提问来源于stack exchange,提问作者user5705458
相关产品推荐
相关产品推荐

