You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何构建优质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
    }
}

额外优化建议

  • 可选值处理:在使用descricaourlImagem这些可选属性时,最好提供默认值(比如上面的?? "无名称"),避免UI出现空值。
  • 线程安全:如果你的项目是多线程环境下访问数据,可以给categories的读写加上线程锁,或者用DispatchQueue保证线程安全。
  • 数据更新通知:如果数据需要动态刷新,可以用NotificationCenter发送通知,让监听的UI组件自动刷新。比如在APIDataManagerloadCategories方法中,数据更新后添加:
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

火山引擎 最新活动