Swift中混合NSManagedObject数组排序的更优实现方案咨询
嘿,我懂你这种感受——Objective-C里用排序描述符轻松搞定的混合Core Data对象排序,到Swift里写起来居然要这么多行代码,确实有点麻烦。不过咱们可以通过几种方式来简化这个逻辑,让代码更简洁、易读,甚至更高效,一起来看看:
方案一:用协议统一类型(最推荐,类型安全+扩展性强)
既然Incident和Transfer都有createdDate属性,咱们可以定义一个协议来统一这个接口,让两个类都遵守它。这样排序的时候就不用反复判断类型和强制转换了:
// 定义协议,统一获取创建日期的方法 protocol HasCreatedDate { var createdDate: NSDate? { get } } // 让你的NSManagedObject子类遵守协议 extension Incident: HasCreatedDate {} extension Transfer: HasCreatedDate {} // 排序逻辑瞬间简洁 var orderMOs = [NSManagedObject]() orderMOs.append(contentsOf: incidentMOs) orderMOs.append(contentsOf: transferMOs) orderMOs.sort { left, right in guard let leftDate = (left as? HasCreatedDate)?.createdDate, let rightDate = (right as? HasCreatedDate)?.createdDate else { return true // 处理日期为空的情况,和你原来的逻辑一致 } return leftDate.compare(rightDate as Date) == .orderedDescending }
这种方式的好处是类型安全,不会因为写错属性名出问题,而且如果以后新增其他带createdDate的Core Data对象,只需要让它遵守协议就行,排序逻辑不用改。
方案二:利用NSManagedObject的KVC特性(最简洁)
因为NSManagedObject本身支持键值编码(KVC),如果两个类的日期属性键名都是createdDate,那可以直接用value(forKey:)来获取,代码会非常短:
var orderMOs = [NSManagedObject]() orderMOs.append(contentsOf: incidentMOs) orderMOs.append(contentsOf: transferMOs) orderMOs.sort { left, right in guard let leftDate = left.value(forKey: "createdDate") as? NSDate, let rightDate = right.value(forKey: "createdDate") as? NSDate else { return true } return leftDate.compare(rightDate as Date) == .orderedDescending }
这个写法最简洁,但要注意键名必须完全匹配,如果以后属性名改了,这里容易漏改导致bug,属于“简洁但牺牲一点类型安全”的方案,适合快速实现或者确定属性名不会变的场景。
方案三:优化原有闭包的写法(最小改动)
如果不想改其他代码,只是优化现有排序闭包的冗余逻辑,可以用if let合并判断,减少重复代码:
var orderMOs = [NSManagedObject]() orderMOs.append(contentsOf: incidentMOs) orderMOs.append(contentsOf: transferMOs) orderMOs.sort { leftMO, rightMO in let leftDate: NSDate? = { if let incident = leftMO as? Incident { return incident.createdDate } else if let transfer = leftMO as? Transfer { return transfer.createdDate } return nil }() let rightDate: NSDate? = { if let incident = rightMO as? Incident { return incident.createdDate } else if let transfer = rightMO as? Transfer { return transfer.createdDate } return nil }() guard let lDate = leftDate, let rDate = rightDate else { return true } return lDate.compare(rDate as Date) == .orderedDescending }
这种方式只是把重复的类型判断逻辑抽成了闭包,代码看起来更整洁,但本质还是和你原来的逻辑一样,没有解决类型转换的冗余问题。
性能对比
这几种方案的性能差异其实很小,因为排序的时间主要消耗在比较逻辑上。不过协议方式因为是直接属性访问,比KVC略快一点(KVC需要做字符串查找和类型转换),但在大多数场景下这种差异可以忽略不计。优先推荐协议方案,兼顾类型安全和简洁性。
内容的提问来源于stack exchange,提问作者RunLoop

