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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 04:10:14