如何对比较值为自定义类型的SortDescriptor进行编解码?
我最近也碰到过一模一样的问题,咱们先还原一下场景:假设我们定义了一个带标题和日期的自定义Item类型:
struct Item { let title: String let date: Date }
官方文档明确说SortDescriptor<Item>默认就符合Codable协议,所以上面的代码编译完全没问题。于是我们尝试编码一个针对Item.title的本地化标准排序描述器:
do { let descriptor = SortDescriptor<Item>(\.title, comparator: .localizedStandard) let encoded = try JSONEncoder().encode(descriptor) print(String(data: encoded, encoding: .utf8)!) } catch { debugPrint(error) }
结果尴尬的事情来了——编译一路绿灯,但运行时直接编码失败,抛出的错误信息如下:
Swift.EncodingError.invalidValue(Foundation.SortDescriptor<__lldb_expr_46.Item>.AllowedComparison.comparableString((extension in Foundation):Swift.String.StandardComparator(options: __C.NSStringCompareOptions(rawValue: 833), isLocalized: true, order: Foundation.SortOrder.reverse), \Item.title), Swift.EncodingError.Context(codingPath: [], debugDescription: "Encoding SortDescriptor with values of type non-NSObject Compared is unsupported.", underlyingError: nil))
问题根源
其实报错信息已经把原因说得明明白白:针对非NSObject类型的SortDescriptor编码是不被原生支持的。虽然SortDescriptor本身标注了Codable,但这只是“部分支持”——当你用的是NSString这类NSObject子类的属性时没问题,但咱们的Item.title是Swift原生的String,即便它能和NSString桥接,编码时底层也没处理这种桥接逻辑,直接导致运行时崩溃。
靠谱的解决方案
既然原生的路走不通,我们可以换个思路:把排序的核心信息(键路径、排序顺序、比较器类型)抽出来做成一个完全可控的Codable自定义结构体,再通过这个结构体生成对应的SortDescriptor。
举个实际的例子:
// 定义可编码的排序键、顺序、比较器枚举 enum SortKey: String, Codable { case title, date } enum SortOrder: String, Codable { case ascending, descending } enum SortComparator: String, Codable { case localizedStandard, standard } // 封装排序信息的可编码结构体 struct CodableSortInfo: Codable { let key: SortKey let order: SortOrder let comparator: SortComparator } // 扩展生成SortDescriptor extension CodableSortInfo { func makeSortDescriptor() -> SortDescriptor<Item> { switch key { case .title: let comparator: String.StandardComparator = comparator == .localizedStandard ? .localizedStandard : .standard let sortOrder: SortOrder = order == .ascending ? .forward : .reverse return SortDescriptor(\.title, comparator: comparator, order: sortOrder) case .date: let sortOrder: SortOrder = order == .ascending ? .forward : .reverse return SortDescriptor(\.date, order: sortOrder) } } }
现在我们就可以安全地编解码排序信息,再转换成需要的SortDescriptor:
do { // 编码排序信息 let sortInfo = CodableSortInfo(key: .title, order: .ascending, comparator: .localizedStandard) let encodedData = try JSONEncoder().encode(sortInfo) print(String(data: encodedData, encoding: .utf8)!) // 输出:{"key":"title","order":"ascending","comparator":"localizedStandard"} // 解码并生成SortDescriptor let decodedInfo = try JSONDecoder().decode(CodableSortInfo.self, from: encodedData) let descriptor = decodedInfo.makeSortDescriptor() // 现在这个descriptor就可以正常使用啦 } catch { debugPrint(error) }
这种方式虽然需要额外写一些代码,但胜在稳定可控,完全避开了原生SortDescriptor编码的坑。
备注:内容来源于stack exchange,提问作者parapote

