SwiftUI ForEach遍历大型本地JSON药品列表卡顿及排序优化问题
处方药品搜索功能性能与排序问题
基础场景
- 输入框监听用户输入的药品名称,输入字符数达到3个时,检索应用内置本地JSON文件
- 本地JSON文件大小4.5MB,共包含3.6万余条药品记录
- 解码后JSON数据对应的结构体仅包含
name、rxcui两个字段 - 搜索结果使用基础
ForEach实现渲染 - 整个功能无外部网络接口调用
- 已尝试添加
.id(UUID())的常规优化方案,未取得任何性能改善 - 功能上线后整体响应极其缓慢,无法满足实时搜索的交互要求
核心需求
- 尽可能提升搜索交互响应流畅度,实现无卡顿的实时搜索体验
- 搜索结果需按药品名称排序,排序逻辑需正确识别字符串中的数值大小(例如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
相关产品推荐
相关产品推荐

