Realm incorrect thread access crash - ios

I am late to this party and newbie to Realm
I have created a signleton class having following method to write but it crashes at times because incorrect thread access
Let me know what I am doing wrong here.
func save<T:Object>(_ realmObject:T) {
let backgroundQueue = DispatchQueue(label: ".realm", qos: .background)
backgroundQueue.async {
let realm = try! Realm()
try! realm.write {
realm.add(realmObject)
}
}
}

Thanks for asking this question! The incorrect thread access exception is a result of the Realm object being passed through a thread boundary. I recommend reading the documentation on Passing Instances Across Threads and this blog post (specifically the section on thread confinement).
In order to avoid that exception, you'll need to change your code to:
func save<T:Object>(_ realmObject:T) {
let realmObjectRef = ThreadSafeReference(to: realmObject)
let backgroundQueue = DispatchQueue(label: ".realm", qos: .background)
backgroundQueue.async {
guard let realmObject = realm.resolve(realmObjectRef) else {
return // although proper error handling should happen
}
let realm = try! Realm()
try! realm.write {
realm.add(realmObject)
}
}
}
The ThreadSafeReference object, documented here provides you with a thread safe reference for a given Realm object that can be passed through a thread boundary and then resolved back to a thread-confined object once you're safely inside a different thread. I hope this helps and let me know if you need anything else. Cheers!

Related

How to use newBackgroundContext() with URLSession.shared.dataTaskPublisher?

I have created a simple Core Data project at Github to demonstrate my problem:
My test app downloads a JSON list of objects, stores it in Core Data and displays in a SwiftUI List via #FetchRequest.
Because the list of objects has 1000+ elements in my real app, I would like to save the entities into the Core Data on a background thread and not on the main thread.
Preferably I would like to use the same default background thread, which is already used by the URLSession.shared.dataTaskPublisher.
So in the standard Persistence.swift generated by Xcode I have added only 2 lines:
container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
container.viewContext.automaticallyMergesChangesFromParent = true
and in my DownloadManager.swift singleton I call:
static let instance = DownloadManager()
var cancellables = Set<AnyCancellable>()
// How to run this line on the background thread of URLSession.shared.dataTaskPublisher?
let backgroundContext = PersistenceController.shared.container.newBackgroundContext()
private init() {
getTops()
}
func getTops() {
guard let url = URL(string: "https://slova.de/ws/top") else { return }
URLSession.shared.dataTaskPublisher(for: url)
.tryMap(handleOutput)
.decode(type: TopResponse.self, decoder: JSONDecoder())
.sink { completion in
print(completion)
} receiveValue: { [weak self] returnedTops in
for top in returnedTops.data {
// the next line fails with EXC_BAD_INSTRUCTION
let topEntity = TopEntity(context: self!.backgroundContext)
topEntity.uid = Int32(top.id)
topEntity.elo = Int32(top.elo)
topEntity.given = top.given
topEntity.avg_score = top.avg_score ?? 0.0
}
self?.save()
}
.store(in: &cancellables)
}
As you can see in the above screenshot, this fails with
Thread 4: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
because I have added the following "Arguments Passed on Launch" in Xcode:
-com.apple.CoreData.ConcurrencyDebug 1
Could anyone please advise me, how to call the newBackgroundContext() on the proper thread?
UPDATE:
I have tried to workaround my problem as in below code, but the error is the same:
URLSession.shared.dataTaskPublisher(for: url)
.tryMap(handleOutput)
.decode(type: TopResponse.self, decoder: JSONDecoder())
.sink { completion in
print(completion)
} receiveValue: { returnedTops in
let backgroundContext = PersistenceController.shared.container.newBackgroundContext()
for top in returnedTops.data {
// the next line fails with EXC_BAD_INSTRUCTION
let topEntity = TopEntity(context: backgroundContext)
topEntity.uid = Int32(top.id)
topEntity.elo = Int32(top.elo)
topEntity.given = top.given
topEntity.avg_score = top.avg_score ?? 0.0
}
do {
try backgroundContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
UPDATE 2:
I was assuming that when newBackgroundContext() is called, it takes the current thread and then you can use that context from the same thread...
This seems not to be the case and I have to call perform, performAndWait or performBackgroundTask (I have updated my code at Github to do just that).
Still I wonder, if the thread of newBackgroundContext can be the same as of the URLSession.shared.dataTaskPublisher...
It seems like you have figured most of this out yourself.
This seems not to be the case and I have to call perform, performAndWait or performBackgroundTask (I have updated my code at Github to do just that).
What's still not solved is here -
Still I wonder, if the thread of newBackgroundContext can be the same as of the URLSession.shared.dataTaskPublisher...
The URLSession API allows you to provide a custom response queue like following.
URLSession.shared
.dataTaskPublisher(for: URL(string: "https://google.com")!)
.receive(on: DispatchQueue(label: "API.responseQueue", qos: .utility)) // <--- HERE
.sink(receiveCompletion: {_ in }, receiveValue: { (output) in
print(output)
})
OR traditionally like this -
let queue = OperationQueue()
queue.underlyingQueue = DispatchQueue(label: "API.responseQueue", qos: .utility)
let session = URLSession(configuration: .default, delegate: self, delegateQueue: queue)
So ideally, we should pass in the NSManagedObjectContext's DispatchQueue as the reponse queue for URLSession.
The issue is with NSManagedObjectContext API that -
neither allows you to supply a custom DispatchQueue instance from outside.
nor exposes a read-only property for it's internally managed DispatchQueue instance.
We can't access underlying DispatchQueue for NSManagedObjectContext instance. The ONLY EXCEPTION to this rule is .viewContext that uses DispatchQueue.main. Of course we don't want to handle network response decoding and thousands of records being created/persisted on/from main thread.

I want to thread data read from realm in iOS

Reading data from ios to realm and using threads at the same time
But "Realm accessed from incorrect thread." An error occurs
Is there a problem with your code?
let realm = try! Realm()
let readData = realm.objects(DataRealm.self)
for i in 0...readData.count-1 {
DispatchQueue.global().async {
self.parsing()
}
}
You cannot use Realm objects across threads. That's why you get this error.
But you can use references which you can pass across them.
I am not sure how to apply it to your code since there is no part where you use the individual objects from readData. But what you are searching for might be something like this:
let realm = try! Realm()
let readData = realm.objects(DataRealm.self)
for data in readData {
let readDataRef = ThreadSafeReference(to: data)
DispatchQueue.global().async {
self.parsing(readDataReference: readDataRef)
}
}
In your parsing() method you would need to get the object from the reference then:
let data = realm.resolve(readDataReference)

Swift - Realm , completion after successfully adding item

So i have this code that meant to add items into the realm:
static func insertAsynctTest(_ objects: [Object], success: #escaping () -> Void, failure: #escaping () -> Void) {
DispatchQueue.global().async {
autoreleasepool {
if let realm = getInstance() {
do {
try realm.write {
for object in objects {
realm.add(object, update: .modified)
}
success()
}
} catch let error as NSError {
print("Something went wrong: \(error.localizedDescription)")
failure()
}
}
failure()
}
}
}
The closure is a must because i need to know that the action is done.
What happens now is that i don't know if the realm finished adding in his thread, so the success() is a false because the loop finished but the adding isn't , and when i try to fetch the data after i am crushing with the follow error:"Realm accessed from incorrect thread".
Is there a way to know that the adding is done?
Thanks
Realm is not thread safe. "Realm accessed from incorrect thread" means you first initialized realm on a different thread and now you are trying to access realm on a different thread. You should always access realm on the thread that it is initialized first.
If you initialize realm in main thread first, you have to access realm only on main thread.
There is another approach to access realm across threads. It is by using ThreadSafeReference
//If you have a person initialized from a different thread, create a ThreadSafeReference for that object
let personRef = ThreadSafeReference(to: person)
Now you can use that object in any thread
DispatchQueue(label: "com.example.myApp.bg").async {
let realm = try! Realm()
guard let person = realm.resolve(personRef) else {
return // person was deleted
}
try! realm.write {
person.name = "Jane Doe"
}
}
Please refer realm documentation on thread safety.

Accessing realm and filter results in different threads

Hello everyone!
I've run into a big problem. I'm trying to access the realm data in main thread but also since I need to do a lot of synchronizing operations with the data such as update, insert etc I decided to search if there is a possibility to do all of these operations asynchronously but I'm getting different errors for every method that I'm trying to implement.
I'm creating the realm database as follows:
realm = try Realm(configuration: Realm.Configuration(
fileURL: fileURL,
encryptionKey: nil,
schemaVersion: schemaVersion,
objectTypes: objectTypes))
Accessing data as follows:
guard let realm = realm else {
return nil
}
let results = realm.objects(SPUserModel.self)
return Array(results)
Doing all of these upsert operations in the mainthread will raise my memory up to 1GB which is bad. Making them asynchronously may be a solution but there are different threads and that's an issue.
Would be glad if you can help me with this.
extension Realm {
class func realmInstance() -> Realm? {
let realm = try Realm(configuration: Realm.Configuration(
fileURL: fileURL,
encryptionKey: nil,
schemaVersion: schemaVersion,
objectTypes: objectTypes))
return realm
}
access :
DispatchQueue(label: "background").async {
Realm.realmInstance()?.objects(SPUserModel.self)
}

Why app is blocked by semaphore?

I have the following function that suppose to return [CIImage] for my purpose - displaying some metadata of photos in tableView.
func getCIImages() -> [CIImage] {
var images = [CIImage]()
let assets = PHAsset.fetchAssetsWithMediaType(.Image, options: nil)
for i in 0..<assets.count {
guard let asset = assets[i] as? PHAsset else {fatalError("Cannot cast as PHAsset")}
let semaphore = dispatch_semaphore_create(0)
asset.requestContentEditingInputWithOptions(nil) { contentEditingInput, _ in
//Get full image
guard let url = contentEditingInput?.fullSizeImageURL else {return}
guard let inputImage = CIImage(contentsOfURL: url) else {return}
images.append(inputImage)
dispatch_semaphore_signal(semaphore)
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}
return images
}
but it stucks in semaphore wait and didn't go further. I have walked through many tutorials but other variants of GCD don't works. I think it's because of simulator, I don't know, can't test on real device. Please help.
guards inside requestContentEditingInputWithOptions callback closure prevents signal sent to semaphore.
In such cases (when you need cleanup actions) it is good to use defer. In your case:
asset.requestContentEditingInputWithOptions(nil) { contentEditingInput, _ in
defer { dispatch_semaphore_signal(semaphore) }
//Get full image
guard let url = contentEditingInput?.fullSizeImageURL else {return}
guard let inputImage = CIImage(contentsOfURL: url) else {return}
images.append(inputImage)
}
UPDATE
Apart from cleanup bug there is another one. Completion closure of requestContentEditingInputWithOptions called on main thread. Which means that if you blocking main thread with semaphore: completion closure is blocked form executing as well. To fix blocked semaphore issue you need call getCIImages on a different thread than main.
Anyway making asynchronous things synchronous is wrong. You should think of different approach.

Resources