Swift 4中网络代码与ViewController分离及数据传递方案问询
嘿,作为同样有Web开发背景的iOS开发者,太懂你这种不想让ViewController变成“万能垃圾桶”的心情了!咱们直接来解决你的问题:
一、通用网络服务的实现(告别重复URLSession代码)
完全不需要为每个端点单独写类,你可以创建一个通用的NetworkService,它能处理各种HTTP请求,支持传入自定义参数、HTTP方法、请求体,并且自动解析响应到你的数据模型里。核心思路是利用Swift 4的Codable协议和泛型,让同一个方法适配不同的请求场景。
第一步:写通用的NetworkService类
import Foundation enum NetworkError: Error { case invalidURL case requestFailed(Error) case invalidResponse case decodingFailed(Error) } class NetworkService { static let shared = NetworkService() // 单例,也可以用依赖注入 // 通用请求方法,支持任意Codable响应模型 func request<T: Codable>( urlString: String, method: String = "GET", parameters: [String: Any]? = nil, headers: [String: String]? = nil, completion: @escaping (Result<T, NetworkError>) -> Void ) { guard let url = URL(string: urlString) else { completion(.failure(.invalidURL)) return } var request = URLRequest(url: url) request.httpMethod = method headers?.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) } // 处理参数:GET拼URL,POST放请求体 if let parameters = parameters { if method == "GET" { var components = URLComponents(url: url, resolvingAgainstBaseURL: true) components?.queryItems = parameters.map { URLQueryItem(name: $0.key, value: "\($0.value)") } guard let updatedURL = components?.url else { completion(.failure(.invalidURL)) return } request.url = updatedURL } else { do { request.httpBody = try JSONSerialization.data(withJSONObject: parameters) request.setValue("application/json", forHTTPHeaderField: "Content-Type") } catch { completion(.failure(.requestFailed(error))) return } } } URLSession.shared.dataTask(with: request) { data, response, error in if let error = error { completion(.failure(.requestFailed(error))) return } guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { completion(.failure(.invalidResponse)) return } guard let data = data else { completion(.failure(.invalidResponse)) return } do { let decoded = try JSONDecoder().decode(T.self, from: data) completion(.success(decoded)) } catch { completion(.failure(.decodingFailed(error))) } }.resume() } }
第二步:定义你的数据模型(遵循Codable)
比如用户资料模型:
struct UserProfile: Codable { let id: Int let name: String let email: String // 其他字段... }
第三步:在任何地方调用这个服务
不管是ViewController还是ViewModel,都可以用这个通用方法:
// 示例:获取用户资料 NetworkService.shared.request(urlString: "https://your-api.com/user/profile", parameters: ["userId": 123]) { (result: Result<UserProfile, NetworkError>) in switch result { case .success(let profile): DispatchQueue.main.async { // 更新UI,比如给ViewController的label赋值 self.nameLabel.text = profile.name } case .failure(let error): print("请求失败:\(error)") // 展示错误提示 } }
二、数据加载的两种模式:该让控制器请求还是先传数据?
这取决于你的场景,两种方案都有适用场景:
1. 先获取数据,再传入控制器(推荐用于纯展示场景)
如果你的ViewController只负责展示数据,不需要触发刷新、提交等交互,最好是在外部(比如导航控制器、ViewModel)先请求数据,再初始化ViewController并传入数据。这样控制器完全和网络逻辑解耦,只专注UI:
// 在导航逻辑里先请求数据 NetworkService.shared.request(urlString: "https://your-api.com/user/profile") { result in switch result { case .success(let profile): DispatchQueue.main.async { let profileVC = ProfileViewController(profile: profile) self.navigationController?.pushViewController(profileVC, animated: true) } case .failure(let error): // 处理错误 } } // ProfileViewController的初始化 class ProfileViewController: UIViewController { private let profile: UserProfile init(profile: UserProfile) { self.profile = profile super.init(nibName: nil, bundle: nil) } override func viewDidLoad() { super.viewDidLoad() // 直接用profile更新UI nameLabel.text = profile.name } }
2. 控制器调用服务请求数据(适用于有交互的场景)
如果控制器需要处理下拉刷新、按钮触发的请求(比如修改资料后重新加载),可以让控制器持有NetworkService的实例(最好用依赖注入,方便测试),然后调用服务请求数据,但控制器里依然不写任何URLSession相关代码:
class ProfileViewController: UIViewController { private let networkService: NetworkService private var profile: UserProfile? // 依赖注入,而不是直接用单例,方便单元测试时传入Mock服务 init(networkService: NetworkService = .shared) { self.networkService = networkService super.init(nibName: nil, bundle: nil) } override func viewDidLoad() { super.viewDidLoad() loadProfile() } private func loadProfile() { networkService.request(urlString: "https://your-api.com/user/profile") { [weak self] result in guard let self = self else { return } DispatchQueue.main.async { switch result { case .success(let profile): self.profile = profile self.updateUI() case .failure(let error): // 展示错误弹窗 } } } } private func updateUI() { guard let profile = profile else { return } nameLabel.text = profile.name } }
三、额外优化建议
- 引入ViewModel:如果业务逻辑复杂,可以加一层ViewModel,让ViewModel负责调用NetworkService处理数据,ViewController只和ViewModel交互,进一步解耦。
- Mock网络服务:在单元测试时,你可以写一个MockNetworkService实现同样的协议,返回假数据,不用依赖真实API。
- 处理线程:记得在回调里切回主线程更新UI,上面的示例已经做了。
内容的提问来源于stack exchange,提问作者user7723378
相关产品推荐
相关产品推荐

