iOS中不同ViewController非同时调用同一Web Service的最优方案问询
嘿,这个问题问到点子上了——重复写网络请求代码不仅冗余,后期维护起来简直是噩梦!在iOS开发里,解决这类跨ViewController复用Web Service逻辑的问题,有几种非常成熟的架构和设计模式,我给你一步步拆解:
1. 最基础:封装独立的网络服务类(单例/依赖注入)
这是入门级的解决方案,核心思路是把所有Web Service的请求逻辑抽离到一个专门的类里,ViewController只负责调用这个类的方法,不用关心底层的网络细节。
示例代码:
// 封装网络服务类 class APIService { // 单例模式(快速上手,但测试时可以用依赖注入替代) static let shared = APIService() private init() {} // 禁止外部实例化 // 封装具体的Web Service请求 func fetchUserData(userId: String, completion: @escaping (Result<User, APIError>) -> Void) { guard let url = URL(string: "https://api.example.com/users/\(userId)") else { completion(.failure(.invalidURL)) return } let task = URLSession.shared.dataTask(with: url) { data, response, error in DispatchQueue.main.async { if let error = error { completion(.failure(.networkError(error))) return } guard let data = data else { completion(.failure(.noData)) return } do { let user = try JSONDecoder().decode(User.self, from: data) completion(.success(user)) } catch { completion(.failure(.parsingError(error))) } } } task.resume() } } // 统一错误类型,方便全局处理 enum APIError: Error { case invalidURL case networkError(Error) case noData case parsingError(Error) } // 数据模型 struct User: Codable { let id: String let name: String let email: String }
ViewController里的调用方式:
class UserProfileVC: UIViewController { override func viewDidLoad() { super.viewDidLoad() // 直接调用封装好的方法 APIService.shared.fetchUserData(userId: "123") { [weak self] result in switch result { case .success(let user): self?.updateUIFromUser(user) case .failure(let error): self?.showErrorAlert(error) } } } private func updateUIFromUser(_ user: User) { // 更新UI逻辑 } private func showErrorAlert(_ error: APIError) { // 错误提示逻辑 } }
2. 进阶:Repository模式(适配复杂业务场景)
如果你的App需要处理本地缓存+网络请求的混合逻辑,或者需要切换不同的数据来源(比如测试环境/生产环境),Repository模式会更合适。它相当于在网络层和ViewController之间加了一层,负责统一数据获取的入口。
示例代码:
// 定义Repository协议,抽象数据获取逻辑 protocol UserRepository { func getUser(userId: String, completion: @escaping (Result<User, APIError>) -> Void) } // 远程数据实现(调用网络服务) class RemoteUserRepository: UserRepository { private let apiService: APIService // 依赖注入,方便替换测试用的Mock服务 init(apiService: APIService = .shared) { self.apiService = apiService } func getUser(userId: String, completion: @escaping (Result<User, APIError>) -> Void) { apiService.fetchUserData(userId: userId, completion: completion) } } // 带缓存的Repository(先读缓存,再后台更新) class CachedUserRepository: UserRepository { private let remoteRepo: RemoteUserRepository private let localCache: UserCache init(remoteRepo: RemoteUserRepository = .init(), localCache: UserCache = .shared) { self.remoteRepo = remoteRepo self.localCache = localCache } func getUser(userId: String, completion: @escaping (Result<User, APIError>) -> Void) { // 优先返回缓存数据 if let cachedUser = localCache.getUser(for: userId) { completion(.success(cachedUser)) // 后台静默更新缓存 remoteRepo.getUser(userId: userId) { result in if case .success(let user) = result { self.localCache.saveUser(user) } } return } // 缓存为空时请求网络 remoteRepo.getUser(userId: userId) { [weak self] result in if case .success(let user) = result { self?.localCache.saveUser(user) } completion(result) } } } // 本地缓存类(示例用UserDefaults,实际可以用CoreData/Realm) class UserCache { static let shared = UserCache() private let defaults = UserDefaults.standard private let keyPrefix = "user_" func getUser(for userId: String) -> User? { guard let data = defaults.data(forKey: keyPrefix + userId) else { return nil } return try? JSONDecoder().decode(User.self, from: data) } func saveUser(_ user: User) { guard let data = try? JSONEncoder().encode(user) else { return } defaults.set(data, forKey: keyPrefix + user.id) } }
ViewController里的调用方式:
class UserProfileVC: UIViewController { // 依赖注入Repository,方便单元测试时替换为Mock实现 private let userRepo: UserRepository init(userRepo: UserRepository = CachedUserRepository()) { self.userRepo = userRepo super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() userRepo.getUser(userId: "123") { [weak self] result in // 处理结果逻辑和之前一致 } } }
3. 高级:结合MVVM架构
如果你的App页面逻辑复杂,想让ViewController只专注于UI展示,可以把网络请求逻辑放到ViewModel里,ViewController通过绑定ViewModel的状态来更新UI,进一步解耦。
示例代码:
class UserViewModel { private let userRepo: UserRepository // 定义状态回调,通知ViewController更新UI var onUserLoaded: ((User) -> Void)? var onError: ((APIError) -> Void)? init(userRepo: UserRepository = CachedUserRepository()) { self.userRepo = userRepo } func loadUser(userId: String) { userRepo.getUser(userId: userId) { [weak self] result in switch result { case .success(let user): self?.onUserLoaded?(user) case .failure(let error): self?.onError?(error) } } } }
ViewController里的调用方式:
class UserProfileVC: UIViewController { private let viewModel = UserViewModel() @IBOutlet weak var userNameLabel: UILabel! @IBOutlet weak var emailLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() setupBindings() viewModel.loadUser(userId: "123") } private func setupBindings() { viewModel.onUserLoaded = { [weak self] user in self?.userNameLabel.text = user.name self?.emailLabel.text = user.email } viewModel.onError = { [weak self] error in self?.showErrorAlert(error) } } }
额外的最佳实践
- 取消请求:如果ViewController销毁时网络请求还在进行,记得取消任务(可以在ViewModel里保存
URLSessionTask实例,在ViewController的deinit里调用cancel())。 - 统一请求拦截:可以在网络层统一处理请求头、Token刷新、全局错误(比如401跳转登录页)。
- 避免单例滥用:单例虽然方便,但不利于测试,优先用依赖注入的方式传递服务实例。
内容的提问来源于stack exchange,提问作者Piyush Vaish
相关产品推荐
相关产品推荐

