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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:36:15