Swift解决SQLite中文排序的问题

SQLite中文排序的问题来自SQLite库内部默认使用Unicode排序,Unicode跟GB系列的编码不同,完全没有按照拼音或部首方式编制,所以直接ORDER BY出来的顺序会比较奇怪。

有人建议用拼音字段方式处理,也算一种思路,但效果不彰且不治本。

另外一种方式就是使用SQLite本身提供的sqlite3_create_collation函数注册一个collation,比如叫pinyin,然后在ORDER BY的时候指定,比如

SELECT * FROM table ORDER BY name COLLATE pinyin

据说Android的SQLite库中默认有LOCALIZED collation,只需要在排序中指定就可以,但苹果系统没有,所以还需要自行指定。

要说的是,这里面几个坑:

1. 调用位置

sqlite3_create_collation直接调用是无效的,需要在sqlite3_collation_needed中调用,而sqlite3_collation_needed必须在数据库open以后调用才有效。基本流程是:

open database -> 调用sqlite3_collation_needed -> 数据库操作(SELECT) -> 调用sqlite3_collation_needed的回调 -> 调用sqlite3_create_collation注册 -> 调用sqlite3_create_collation注册的回调。

2. 类型转换

Swift调用C函数就是一大堆的UnsafePointer的类型,不过好在sqlite3_create_collation的参数都是sqlite3_collation_needed回调函数的参数,需要只有两个参数而已,具体如下:

sqlite3_collation_needed(OpaquePointer(db.sqliteHandle), nil) { (pArg, sqlite, eTextRep, name) in

            sqlite3_create_collation(sqlite, name, eTextRep, pArg) { (pArg, nKey1, pKey1, nKey2, pKey2) -> Int32 in

这里使用了FMDB,db类型为FMDatabase,没有使用自定义参数所以为空。

此外就是sqlite3_create_collation传入的字符串参数转换的问题,pKey1、pKey2都是UnsafeRawPointer,需要转换成String才方便比较,方法如下:

String.init(cString: pKey1!.assumingMemoryBound(to: CChar.self))

3.比较函数

要注意的是直接字符串比较是不行的,因为Swift内部保存也是用的Unicode,直接比较就和SQLite的默认实现一样了,理论上说,我们应该把要比较两个字符串都转成GB系列的编码,然后再进行比较才行。不过,String本身提供了一个很方便的函数来解决这个问题,localizedCompare,也就是不使用Unicode,而使用当前系统的编码比较字符串,于是问题解决。

 

4.全部代码

let db = FMDatabase(path: tmpURL.path)

db.open()

sqlite3_collation_needed(OpaquePointer(db.sqliteHandle), nil) { (pArg, sqlite, eTextRep, name) in

    sqlite3_create_collation(sqlite, name, eTextRep, pArg) { (pArg, nKey1, pKey1, nKey2, pKey2) -> Int32 in

        let s1 = String.init(cString: pKey1!.assumingMemoryBound(to: CChar.self))

        let s2 = String.init(cString: pKey2!.assumingMemoryBound(to: CChar.self))

        return Int32(s1.localizedCompare(s2).rawValue)

    }

}

 

最后,别忘了在SQL语句里面COLLATE xxx哦,希望对您有所帮助。

Leave a Reply

Your email address will not be published. Required fields are marked *