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

SwiftUI ForEach遍历大型本地JSON药品列表卡顿及排序优化问题

处方药品搜索功能性能与排序问题

基础场景

  • 输入框监听用户输入的药品名称,输入字符数达到3个时,检索应用内置本地JSON文件
  • 本地JSON文件大小4.5MB,共包含3.6万余条药品记录
  • 解码后JSON数据对应的结构体仅包含name、rxcui两个字段
  • 搜索结果使用基础ForEach实现渲染
  • 整个功能无外部网络接口调用
  • 已尝试添加.id(UUID())的常规优化方案,未取得任何性能改善
  • 功能上线后整体响应极其缓慢,无法满足实时搜索的交互要求

核心需求

  1. 尽可能提升搜索交互响应流畅度,实现无卡顿的实时搜索体验
  2. 搜索结果需按药品名称排序,排序逻辑需正确识别字符串中的数值大小(例如Tylenol 50MG需排在Tylenol 100MG之前)

已实现代码与尝试过的方案

数值排序逻辑初版

曾调用compare方法实现数值维度排序,排序逻辑符合预期,但进一步加剧了性能卡顿,对应实现代码如下:

var allDrugsSorted: [Mod_Prescription_DrugsList] {
    return Mod_Prescription_DrugsList.allDrugs.sorted { first, second in
        first.name.compare(second.name, options: .numeric) == .orderedAscending
    } // End Return
}

当前搜索结果渲染代码

if isFocused == .prescription_name &&
viewModel.prescriptionName.count >= 3 {
    ForEach(
        viewModel.allDrugsSorted.filter {
            $0.name
                .lowercased()
                .hasPrefix(
                    viewModel.prescriptionName.lowercased()
                )
        }.prefix(5), id: \.self) { prescription in
            Button {
                isFocused = nil
            } label: {
                VStack(alignment: .leading) {
                    Text(LocalizedStringKey(prescription.name))
                }
                .frame(maxWidth: .infinity, alignment: .leading)
            } // End Label
            .contentShape(Rectangle())
            .padding()
    } // End ForEach
} // End If

数据加载层实现

药品数据结构体定义

struct Mod_Prescription_DrugsList: Codable, Hashable {
    let rxcui: String
    let name: String

    static let allDrugs = Bundle.main.decode([Mod_Prescription_DrugsList].self, from: "Drugs.json") 
    static let example = allDrugs[0]
}

Bundle 解码扩展实现

extension Bundle {
    func decode<T: Decodable>(
        _ type: T.Type,
        from file: String,
        dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate,
        keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys
    ) -> T {
        guard let url = self.url(
            forResource: file,
            withExtension: nil
        ) else {
            fatalError("Failed to locate \(file) in bundle.")
        }

        guard let data = try? Data(
            contentsOf: url
        ) else {
            fatalError("Failed to load \(file) from bundle.")
        }

        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = dateDecodingStrategy
        decoder.keyDecodingStrategy = keyDecodingStrategy

        do {
            return try decoder.decode(T.self, from: data)
        } catch DecodingError.keyNotFound(let key, let context) {
            fatalError("Failed to decode \(file) from bundle due to missing key '\(key.stringValue)' - \(context.debugDescription)")
        } catch DecodingError.typeMismatch(_, let context) {
            fatalError("Failed to decode \(file) from bundle due to type mismatch - \(context.debugDescription)")
        } catch DecodingError.valueNotFound(let type, let context) {
            fatalError("Failed to decode \(file) from bundle due to missing \(type) value - \(context.debugDescription)")
        } catch DecodingError.dataCorrupted(_) {
            fatalError("Failed to decode \(file) from bundle because it appears to be invalid JSON")
        } catch {
            fatalError("Failed to decode \(file) from bundle - \(error.localizedDescription)")
        }
    }
}

预加载排序尝试

曾尝试在ViewModel初始化阶段提前完成JSON的解码与排序生成数组,该方案虽小幅提升了搜索时的响应速度,但会导致视图加载耗时接近1分钟,完全无法正常使用。其中self.allDrugsArray是ViewModel中定义的@Published属性,对应实现方法如下:

func getAllDrugs() {
    let sortedDrugs = Mod_Prescription_DrugsList.allDrugs.sorted { first, second in
        first.name.compare(second.name, options: .numeric) == .orderedAscending
    }
    sortedDrugs.forEach { drug in
        self.allDrugsArray.append(drug)
    }
}

内容的提问来源于stack exchange,提问作者kittonian

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 09:30:46