在内存领域丢失数据,即使我从同一个线程调用

In memory realm loses data even though I am calling from same thread

注意:我看过其他帖子,但我的问题有点不同

我有一个助手 class 可以访问 Realm。 class 中的每个函数都会创建它自己的 Realm 对象实例,以避免线程问题,具体来说 Realm accessed from incorrect thread.;这对磁盘领域非常有效;但是,对于我的内存领域,数据已成功插入,但是当我尝试检索它时,我什么也得不到。我想也许 Realm 是从不同的线程访问的,所以我所做的是我创建了一个 DispatchQueue 并且我总是从那个队列访问领域。

这是我的代码

protocol Cachable {}

protocol InMemoryCache {
    func create<T: Cachable>(model: T.Type,
                             _ completion: @escaping (Result<T, Error>) -> ())
    
    func save(object: Cachable,
              _ completion: @escaping (Result<Void, Error>) -> ())
    
    func fetch<T: Cachable>(model: T.Type,
                            predicate: NSPredicate?,
                            sorted: Sorted?,
                            _ completion: @escaping (Result<[T], Error>) -> ())
}
    

enum RealmInMemoryCacheError: Error {
    case notRealmSpecificModel
    case realmIsNil
    case realmError
}


final class RealmInMemoryCache {

    private let configuration: Realm.Configuration
    private let queue: DispatchQueue

    init(_ configuration: Realm.Configuration) {
        self.queue = DispatchQueue(label: "inMemoryRealm", qos: .utility)
        self.configuration = configuration
    }
}

extension RealmInMemoryCache : InMemoryCache{
    func create<T>(model: T.Type,
                   _ completion: @escaping (Result<T, Error>) -> ()) where T : Cachable {
        self.queue.async {
            guard let realm = try? Realm(configuration: self.configuration) else {
                return completion(.failure(RealmInMemoryCacheError.realmIsNil))
            }
            
            guard let model = model as? RealmSwift.Object.Type else {
                return completion(.failure(RealmInMemoryCacheError.notRealmSpecificModel))
            }
        
        
            do {
                try realm.write { () -> () in
                    let newObject = realm.create(model, value: [], update: .all) as! T
                    return completion(.success(newObject))
                }
            } catch {
                return completion(.failure(RealmInMemoryCacheError.realmError))
            }
        }
    }
    
    func save(object: Cachable,
              _ completion: @escaping (Result<Void, Error>) -> ()) {
        self.queue.async {
            guard let realm = try? Realm(configuration: self.configuration) else {
                return completion(.failure(RealmInMemoryCacheError.realmIsNil))
            }
            
            guard let object = object as? RealmSwift.Object else {
                return completion(.failure(RealmInMemoryCacheError.notRealmSpecificModel))
            }
        
        
            do {
                try realm.write { () -> () in
                    realm.add(object, update: .all)
                    return completion(.success(()))
                }
            } catch {
                return completion(.failure(RealmInMemoryCacheError.realmError))
            }
        }
    }
    
    func fetch<T>(model: T.Type,
                  predicate: NSPredicate?,
                  sorted: Sorted?,
                  _ completion: @escaping (Result<[T], Error>) -> ()) where T : Cachable {
        self.queue.async {
            guard let realm = try? Realm(configuration: self.configuration) else {
                return completion(.failure(RealmInMemoryCacheError.realmIsNil))
            }
            
            guard
                let model = model as? RealmSwift.Object.Type else {
                return completion(.failure(RealmInMemoryCacheError.notRealmSpecificModel))
            }
            
            
            var objects = realm.objects(model)
            
            if let predicate = predicate {
                objects = objects.filter(predicate)
            }

            if let sorted = sorted {
                objects = objects.sorted(byKeyPath: sorted.key, ascending: sorted.ascending)
            }

            return completion(.success(objects.compactMap { [=10=] as? T}))
        }
    }
}

extension Object: Cachable {}

struct Sorted {
    var key: String
    var ascending: Bool = true
}

我删除了对问题没有任何好处的代码,因此您会在上面的代码中看到 empty/missing 内容。但是,上面的代码可以 100% 复制和粘贴。

我尝试在初始化中创建领域,所以我对它有很强的参考;但是,这会导致线程安全问题,它可能会工作几次,但有时会由于错误 Realm accessed from incorrect thread.

而使应用程序崩溃

如您所知,我的目标是使上述代码通用且 100% 线程安全,即使是从后台线程(例如在不同的函数中调用)也是如此。其背后的原因是想象上面的 class 是一个 API 并且不同的程序员会使用它,有时他们会在 background 线程上调用一个函数而实际上不知道下面发生了什么引擎盖。如果他们这样做,我不希望应用程序崩溃。

编辑:这就是 helper class 的初始化方式

let realmInMemory = RealmInMemoryCache(Realm.Configuration(inMemoryIdentifier: "globalInMemoryRealm")

// Then I can use it like so (replace model with your realm model)
realmInMemory.create(model) { result in {
   switch result {
      ...
   }
}

编辑 2:这是上述 class 工作原理的完整示例

import RealmSwift

final class MessageRealmEntity: Object {
    @objc dynamic var id: String = ""
    @objc dynamic var message: String = ""

    convenience init(id: String, message: String) {
        self.init()
        self.id = id
        self.message = message
    }
    
    override static func primaryKey() -> String? {
        "id"
    }
}

// THIS IS NOT PART OF THE PROBLEM, THIS `Main` CLASS IS JUST A DRIVER. THE CODE INSIDE IT COULD RUN ANYWHERE.
final class Main {
    let realmInMemory = RealmInMemoryCache(Realm.Configuration(inMemoryIdentifier: "globalInMemoryRealm"))
    
    func run() {
        DispatchQueue.global(qos: .background).async {
            
            
            let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
            
            var entity = MessageRealmEntity(id: "1", message: "Hello, World!")
            
            self.realmInMemory.save(object: entity) { result in
                switch result {
                case .success(_):
                    print("Saved successfully")
                case .failure(let error):
                    print("Got error")
                }
                semaphore.signal()
            }
            _ = semaphore.wait(wallTimeout: .distantFuture)
            
            
            self.realmInMemory.fetch(model: MessageRealmEntity.self, predicate: nil, sorted: nil) { result in
                switch result {
                case .success(let messages):
                    print(messages.count) // This will return 0 when it should be 1 since we inserted  already
                case .failure(let error):
                    print("Got error")
                }
                
                semaphore.signal()
            }
            
            _ = semaphore.wait(wallTimeout: .distantFuture)
        }
    }
}

let main: Main = Main()
main.run()

所有其他方法的调用方式相同。

编辑3:

在反复阅读 Realm 的 documentation 并不断思考@Jay 在评论中所说的内容后,我更加关注文档中的这句话

Notice: When all in-memory Realm instances with a particular identifier go out of scope with no references, all data in that Realm is deleted. We recommend holding onto a strong reference to any in-memory Realms during your app’s lifetime. (This is not necessary for on-disk Realms.)

以上引用中的关键句是

When all in-memory Realm instances with a particular identifier go out of scope with no references, all data in that Realm is deleted.

换句话说,每次我尝试保存或获取或执行任何其他功能时,我的 Realm 对象都会超出范围

例如:

self.queue.async {
            guard let realm = try? Realm(configuration: self.configuration) else {
                completion(.failure(RealmInMemoryCacheError.realmIsNil))
                return
            }
            
            guard let object = object as? RealmSwift.Object else {
                completion(.failure(RealmInMemoryCacheError.notRealmSpecificModel))
                return
            }
        
        
            do {
                try realm.write { () -> () in
                    realm.add(object, update: .all)
                    completion(.success(()))
                    return
                }
            } catch {
                completion(.failure(RealmInMemoryCacheError.realmError))
                return
            }
        }

在上面的代码中,一旦队列完成,Realm 就会超出范围。然后 Realm 将查看内存中是否有任何其他具有相同标识符的变量,如果有则什么也不做,否则它将删除当前领域以进行优化。

所以问题的解决方案基本上是使用此标识符创建对领域的强引用,然后在每个函数中重新创建具有相同标识符的领域,以避免 Realm accessed from incorrect thread 然而这将呈现在一个未使用的额外变量,但我认为目前还可以,至少这是我的解决方案,直到官方来自 Realm。请记住,重新初始化的过程不应成为开销,因为 Realm 负责优化。

这是我所做的

final class RealmInMemoryCache {

    private let configuration: Realm.Configuration
    private let queue: DispatchQueue
    private let strongRealm: Realm     <-- Added variable

    init(_ configuration: Realm.Configuration) {
        self.queue = DispatchQueue(label: "inMemoryRealm", qos: .utility)
        self.configuration = configuration
        self.strongRealm = try! Realm(configuration: self.configuration)    <-- Initialized here
    }
}

在其他函数中,我做了类似下面的事情

...
        self.queue.async {
            guard let realm = try? Realm(configuration: self.configuration) else {
                completion(.failure(RealmInMemoryCacheError.realmIsNil))
                return
            }
...

让我觉得我的 Realm 超出范围的是当我设置断点时 Realm 开始表现得非常好。虽然我仍然不能 100% 确定为什么,但我的想法是,当我在 lldb 中编写命令 po realm.objects(...) 时,xcode 调试器可能会创建对 Realm 的强引用。

我暂时接受这个答案,除非有人有更好的解决方案。