Post

WCDB当设置了setConfig后会导致强引用不释放的问题九)

前言

由于项目性质为IM,数据使用sqlite+wcdb,需要本地数据库管理大量数据,为了性能及查询方便,采用的是每个群都是独立的库,在数据读写时都需要初始化独立的database,当database过多时就需要及时释放,= 在中间进行性能优化时,给每个database设置了setConfig,导致同一个路径的database一直不释放,知道再次创建相同路径的database才会释放,当有大量的单聊、群聊消息时,创建了大量的database又没法及时释放,内存使用过高,导致App被系统杀掉

导致database被循环引用的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class StudentDBWorker {
    let dbPath = PathTool.documentsDir.appendPath("/xx/xx/xx.db")
    deinit {
        print("deinit--StudentDBWorker-\(self)")
    }
    init() {
        print(dbPath)
        let db = Database(at: "/xxx/xx/xx.db")
        do {
            // 只要设置了这个setConfig就会导致被强引用不能释放,如果没有这一段,执行完初始化就会销毁db
            db.setConfig(named: "checkSizeBeforeProcessDemo", withInvocation: { handle in
                try handle.exec(StatementPragma().pragma(.journalSizeLimit).to(1024*8))
            }, withPriority: Database.ConfigPriority.default)
            
            // 要初始化表
            try db.create(table: StudentDBModel.tableName, of: StudentDBModel.self)
        } catch {
            print(error)
        }
    }
    
    @discardableResult
    func getStuCount() -> Int? {
        // 这里重新创建相同路径的database时,会触发之前相同路径的database销毁
        let db = Database(at: "/xxx/xx/xx.db")
        do {
            // 没有on就是全量字段插入
            let val = try db.getValue(on: StudentDBModel.Properties.id.count(), fromTable: StudentDBModel.tableName)
            return val.intValue
        } catch {
            print(error)
            return nil
        }
    }
}

产生这个的原因时WCDBfunc setConfig(named name: String, withInvocation invocation: @escaping Config, withUninvocation uninvocation: Config? = nil, withPriority priority: ConfigPriority = ConfigPriority.default)里代码强引用导致Database.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
func setConfig(named name: String,
                   withInvocation invocation: @escaping Config,
                   withUninvocation uninvocation: Config? = nil,
                   withPriority priority: ConfigPriority = ConfigPriority.default) {
        let cppInvocation: @convention(c) (UnsafeMutableRawPointer, CPPHandle) -> Bool = {
            cppContext, cppHandle in
            let invocationWrap: ValueWrap<(CPPHandle) -> Bool>? = ObjectBridge.extractTypedSwiftObject(cppContext)
            guard let invocationWrap = invocationWrap else {
                return false
            }
            return invocationWrap.value(cppHandle)
        }
//        let invocationBlock: (CPPHandle) -> Bool = { [weak self]
//            cppHandle in
//            guard let self = self else {
//                return false
//            }
//            let handle = Handle(withCPPHandle: cppHandle, database: self)
//            var ret = true
//            do {
//                try invocation(handle)
//            } catch {
//                ret = false
//            }
//            return ret
//        }
        // 这里强引用了self,导致不能释放database
        let invocationBlock: (CPPHandle) -> Bool = {
            cppHandle in
            let handle = Handle(withCPPHandle: cppHandle, database: self)
            var ret = true
            do {
                try invocation(handle)
            } catch {
                ret = false
            }
            return ret
        }
        let invocationWrap = ValueWrap(invocationBlock)
        let invocationWrapPointer = ObjectBridge.getUntypeSwiftObject(invocationWrap)

        var uninvocationWrapPointer: UnsafeMutableRawPointer?
        if let uninvocation = uninvocation {
            let uninvocationBlock: (CPPHandle) -> Bool = { [weak self]
                cppHandle in
                guard let self = self else {
                    return false
                }
                let handle = Handle(withCPPHandle: cppHandle, database: self)
                var ret = true
                do {
                    try uninvocation(handle)
                } catch {
                    ret = false
                }
                return ret
            }
            let uninvocationWrap = ValueWrap(uninvocationBlock)
            uninvocationWrapPointer = ObjectBridge.getUntypeSwiftObject(uninvocationWrap)
        }
        WCDBDatabaseConfig(database,
                           name.cString,
                           cppInvocation,
                           invocationWrapPointer,
                           uninvocationWrapPointer != nil ? cppInvocation : nil,
                           uninvocationWrapPointer,
                           priority.rawValue,
                           ObjectBridge.objectDestructor)
    }

解决方案

修改源码把相关强用引用的self改成弱引用
另外在使用setConfig来处理journal_size_limit处理在性能上是否有影响还没测过

该问题是一个大佬发现定位到的,从解决实际问题的效率速度来来说,真心佩服,挺厉害

This post is licensed under CC BY 4.0 by the author.