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

如何在两个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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 13:43:12