如何构建优质JSON模型实现iOS跨控制器数据访问
如何在iOS项目中全局访问API返回的结构化数据?
看起来你已经完成了API请求和JSON解析的基础工作,但目前的实现只能通过completion回调逐个返回单个分类的描述和图片URL,没法在项目的任意控制器中方便地访问完整的数据集。下面我会一步步帮你优化实现,让你能在任意地方获取并使用这些数据来填充UI组件。
第一步:修正数据模型(符合Swift规范)
首先,你的结构体命名data不符合Swift的大驼峰命名规范,而且我们可以给它更语义化的名字,比如Category,这样代码可读性更强:
import Foundation struct CategoryResponse: Decodable { let data: [Category] } struct Category: Decodable { let id: Int? let descricao: String? let urlImagem: String? }
第二步:创建全局数据管理类
我们可以用单例模式创建一个数据管理类,负责API请求、数据存储,并且让所有控制器都能轻松访问这些数据。这个类还可以提供回调或者通知,让UI组件在数据加载完成后及时更新:
import Foundation import Alamofire class APIDataManager { // 单例实例,全局唯一 static let shared = APIDataManager() // 存储解析后的分类数据 var categories: [Category] = [] // 私有初始化,防止外部创建实例 private init() {} // 加载分类数据的方法,完成后通过completion通知调用者 func loadCategories(completion: @escaping (Error?) -> Void) { guard let url = URL(string: "https://alodjinha.herokuapp.com/categoria") else { completion(NSError(domain: "Invalid URL", code: -1, userInfo: nil)) return } Alamofire.request(url).responseData { response in switch response.result { case .success(let data): do { let response = try JSONDecoder().decode(CategoryResponse.self, from: data) self.categories = response.data completion(nil) } catch { completion(error) } case .failure(let error): completion(error) } } } }
第三步:在项目中使用数据
1. 初始化加载数据
通常你可以在AppDelegate或者SceneDelegate的启动方法中提前加载数据,这样进入页面时数据已经准备好了:
// 在SceneDelegate的scene(_:willConnectTo:options:)方法中 func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { APIDataManager.shared.loadCategories { error in if let error = error { print("加载分类数据失败: \(error.localizedDescription)") } else { print("分类数据加载完成,共\(APIDataManager.shared.categories.count)条") } } // 其他初始化代码... }
2. 在任意控制器中访问数据并填充UI
比如在一个展示分类列表的UITableViewController中:
import UIKit class CategoryListViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() title = "分类列表" tableView.register(UITableViewCell.self, forCellReuseIdentifier: "CategoryCell") // 如果数据还没加载完成,可以重新请求并刷新UI if APIDataManager.shared.categories.isEmpty { APIDataManager.shared.loadCategories { [weak self] error in if error == nil { DispatchQueue.main.async { self?.tableView.reloadData() } } } } } // MARK: - Table view data source override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return APIDataManager.shared.categories.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell", for: indexPath) let category = APIDataManager.shared.categories[indexPath.row] cell.textLabel?.text = category.descricao ?? "无名称" // 示例:如果需要加载图片,可以用第三方库(如Kingfisher)实现 // if let imageUrl = category.urlImagem, let url = URL(string: imageUrl) { // cell.imageView?.kf.setImage(with: url) // } return cell } }
额外优化建议
- 可选值处理:在使用
descricao、urlImagem这些可选属性时,最好提供默认值(比如上面的?? "无名称"),避免UI出现空值。 - 线程安全:如果你的项目是多线程环境下访问数据,可以给
categories的读写加上线程锁,或者用DispatchQueue保证线程安全。 - 数据更新通知:如果数据需要动态刷新,可以用
NotificationCenter发送通知,让监听的UI组件自动刷新。比如在APIDataManager的loadCategories方法中,数据更新后添加:
NotificationCenter.default.post(name: NSNotification.Name("CategoriesUpdated"), object: nil)
然后在控制器中监听并刷新UI:
override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(refreshCategories), name: NSNotification.Name("CategoriesUpdated"), object: nil) } @objc func refreshCategories() { tableView.reloadData() } deinit { NotificationCenter.default.removeObserver(self) }
内容的提问来源于stack exchange,提问作者Bruno Vavretchek




