公司最近正在把iOS端的数据库从Core Data替换成Realm, 期间踩坑无数, 这里主要讲讲排序的问题.
Realm的排序和Core Data比起来简直就是弱爆了, 在Core Data中, 如果想做自然排序, 只需要在 sortDescriptors 中指定 selector 为 NSString.localizedStandardCompare 即可实现自然排序:
fetchRequest.sortDescriptors = [NSSortDescriptor(key: SortOptionFirstName, ascending: true, selector: #selector(NSString.localizedStandardCompare))]
但是Realm提供的对数据进行排序的 sorted(by: ) 方法简陋的一塌糊涂.
sorted(byKeyPath: "name", ascending: ascending)
如果用这种方法排序, 举个简单的例子 5个 Realm Objects 的name为(这也是我们想要的顺序):
Test, TEST, Test1, Test2, Test10
用以上方法排序出来的结果为:
Test, Test1, Test10, Test2, TEST
这个结果是不能接受的, 前两个的顺序其实并不重要, 主要是包含数字的name数字要按照人类理解的顺序来排
那么有什么方法可以实现自然排序呢? 在google搜索一番之后, 看到个令人叹服的解决方案:
其大概的意思是, 查找出字符串里所有的数字, 然后在数字的前面加上数字的位数
所以, 基于这种思想, 大概的解题思路是, 我们在需要用到自然排序的Realm数据模型里加上一条会被保存在Realm数据库中的属性nameForSort, nameForSort 会把name String 里所有的数字(包含小数)的整数部分的位数加在数字前面, 这样排序就不会有问题了.
具体代码如下:
首先是给String 添加一个function:
这里首先使用正则表达式获取String内所有的数字(包含整数和小数)的Range, 然后根据这个Range, 建立一个包含所有数字的数组, 在map整数部分的位数
这样, 所有数字的位置还有整数部分的位数都有了, 下面就是往String里所有数字前面插入整数位数, 这里我们倒着插入, 这样前面的位置就不会变了.
extension String {
func fixedForSort() -> String {
//use regular expression to fetch all digits (integer and pointing float) Range info
let pattern = "\\d+\\.?\\d*"
let regular = try! NSRegularExpression(pattern: pattern, options: .caseInsensitive)
let rangeResults = regular.matches(in: self, options: .reportProgress, range: NSRange(location: 0, length: self.count))
//get the digits array based on the range info
var digitsInString = [String]()
for result in rangeResults {
digitsInString.append((self as NSString).substring(with: result.range))
}
// map the digits array to Integer array then get the number of Integer
let digitsCounts = digitsInString.map({String(Int(Double($0) ?? 0)).count})
//reverse the range Results and digitsCounts for insert from back to front
let rangeResultsReversed: [NSTextCheckingResult] = rangeResults.reversed()
let digitsCountsReversed: [Int] = digitsCounts.reversed()
var fixedString = self
//insert the digitsCounts to the right position
for i in 0..<rangeResultsReversed.count {
fixedString.insert(contentsOf: "\(digitsCountsReversed[i])", at: String.Index(encodedOffset: rangeResultsReversed[i].range.location))
}
return fixedString.localizedLowercase
}
}
我们有了这种方法之后, 在Realm object 数据模型中, 我们这样定义:
@objc dynamic private var real_name: String?
@objc dynamic private var nameForSort: String?
var name: String? {
get {
return real_name
}
set {
real_name = newValue
nameForSort = (newValue ?? "").fixedForSort()
}
}
这样, 我们排序的时候用nameForSort来排序就OK了
当然, 这个idea还不成熟, 还有好多问题没考虑到, 比如, 如果俩个name中有两个整数部分相同, 但是小数部分不同, 这种情况下还是会排序不准. 不过在我们的APP中这种情况出现的概率基本没有, 所以就没多加考虑.
只是记录一种思路, 供需要的人参考
网友评论