在内存领域丢失数据,即使我从同一个线程调用
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:
- 我打开了github问题,如果有人有兴趣关注它,这里是link:https://github.com/realm/realm-cocoa/issues/7017那里有一个视频和更多解释
- 这是一个githublink下载一个重现bug的工程https://github.com/Muhand/InMemoryRealm-Bug
在反复阅读 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 的强引用。
我暂时接受这个答案,除非有人有更好的解决方案。
注意:我看过其他帖子,但我的问题有点不同
我有一个助手 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:
- 我打开了github问题,如果有人有兴趣关注它,这里是link:https://github.com/realm/realm-cocoa/issues/7017那里有一个视频和更多解释
- 这是一个githublink下载一个重现bug的工程https://github.com/Muhand/InMemoryRealm-Bug
在反复阅读 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 的强引用。
我暂时接受这个答案,除非有人有更好的解决方案。