I have two views. The first one shows a list of the custom objects made of downloaded data, the second shows a list of objects that are the basis for objects from the first list.
If I choose an object in the second view to save in Realm and go back to the first one, the data to make a list of custom objects is downloaded from database. If I want to delete that object the app crash and this message appears:
Thread 1: Exception: "Realm accessed from incorrect thread."
The same situation is when I delete one, two or more objects in the first screen, go to another one, choose one, two, or more to save in the database and go back to the first one, where data is downloaded from database. App is crashing and same message.
I know it's all about threads, but I don't know how to resolve that.
I tried resolving that by DispatchQueue, but it doesn't work, or i'm doing it wrong. How to resolve this thread problem in my case?
These database functions are using in the first view:
func deleteAddItem(addItem: AddItem) throws {
do {
let realm = try! Realm()
try! realm.write {
if let itemToDelete = realm.object(ofType: AddItem.self, forPrimaryKey: addItem.id) {
realm.delete(itemToDelete)
realm.refresh()
}
}
}
}
}
func fetchStations() throws -> Results<Station> {
do {
realm = try Realm()
return realm!.objects(Station.self)
}
catch {
throw RuntimeError.NoRealmSet
}
}
func fetchSensors() throws -> Results<Sensor> {
do {
realm = try Realm()
return realm!.objects(Sensor.self)
}
catch {
throw RuntimeError.NoRealmSet
}
}
func fetchAddItems() throws -> Results<AddItem> {
do {
realm = try Realm()
return realm!.objects(AddItem.self)
}
catch {
throw RuntimeError.NoRealmSet
}
}
func fetchData() throws -> Results<Data> {
do {
realm = try Realm()
return realm!.objects(Data.self)
}
catch {
throw RuntimeError.NoRealmSet
}
}
If you want more code or information, please let me know.
It appears you have two different Realm threads going
func deleteAddItem(addItem: AddItem) throws {
do {
let realm = try! Realm() <- One realm thread, a local 'let'
and then
func fetchAddItems() throws -> Results<AddItem> {
do {
realm = try Realm() <- A different realm thread, a class var?
that can probably be fixed by using the same realm when deleting
func deleteAddItem(addItem: AddItem) throws {
do {
realm = try! Realm() <- references the same realm as in delete
There are few options to prevent this. Once is simply get the realm, every time you want to use it
let realm = try! Realm()
realm.read or realm.write etc
or create a singleton function (or a RealmService class) that can be accessed throughout the app. In a separate file (or whever you want to put it and this is just a quick example)
import RealmSwift
func gGetRealm() -> Realm? {
do {
let realm = try Realm()
return realm
} catch let error as NSError { //lots of error handling!
print("Error!")
print(" " + error.localizedDescription)
let err = error.code
print(err)
let t = type(of: err)
print(t)
return nil
}
}
Then to use it
if let realm = gGetRealm() {
realm.read, realm.write etc
}
Also, I noticed you appear to be getting an item from realm to just then delete it. That's not necessary.
If the item is already managed by Realm, it can just be deleted directly. Here's an updated delete function
func deleteItem(whichItem: theItemToDelete) {
if let realm = gGetRealm() {
try! realm.write {
realm.delete(theItemToDelete)
}
}
}
Related
I am trying to save data in CoreData. When the app is running everything is ok. I receive info that data is saved and receive messages that it is fetching correct. But when I close the app data just disappeared somewhere.
When I created the project I do not check Core Data, so I added xcdatamodel, import CoreData everywhere, updated AppDelegate with the correct NSPersistentContainer name (the name is name of my xcdatamodel) also in Project-General-Frameworks added CoreData.framework.
Here is part of saving, fetching, and deleting data. The file is separate from VC. I do not receive any type of errors.
In my VC I just call savedata(), to save the data. It works before the app is closed.
import UIKit
import CoreData
var cgsTeams = [TeamsCoreData]()
func savedata() {
saveTeams { (complete) in
if complete {print("TeamsSaved")}
}
}
func saveTeams(completion: (_ finished: Bool) -> ()) {
guard let managedContext = appDelegate?.persistentContainer.viewContext else {return}
let privateManagedContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateManagedContext.parent = managedContext
for team in teams {
let teamsCoreData = TeamsCoreData(context: privateManagedContext)
teamsCoreData.team = team.name
teamsCoreData.score = Int32(team.score)
teamsCoreData.number = Int32(team.number)
do{
try privateManagedContext.save()
debugPrint("Succesfully saved teamsCoreData")
completion(true)
} catch{
debugPrint("Could not save - \(error)")
completion(false)
}
}
}
func fetchTeams(completion: (_ complete: Bool) -> ()) {
guard let managedContext = appDelegate?.persistentContainer.viewContext else { return }
let fetchRequest = NSFetchRequest<TeamsCoreData>(entityName: "TeamsCoreData")
do {
cgsTeams = try managedContext.fetch(fetchRequest)
print("cgsGameRules fetched")
teams = [team]()
for cgsTeam in cgsTeams {
print("team - \(cgsTeam.team!) added")
teams.append(team(name:cgsTeam.team!, number: Int(cgsTeam.number), score: Int(cgsTeam.score)))
}
if cgsTeams.count > 1{completion(true)} else {completion (false); print("No teams")}
} catch {
debugPrint("Could not fetch: \(error.localizedDescription)")
completion(false)
}
}
func deleteTeams(){
guard let managedContext = appDelegate?.persistentContainer.viewContext else { return }
let fetchRequest = NSFetchRequest<TeamsCoreData>(entityName: "TeamsCoreData")
let objects = try! managedContext.fetch(fetchRequest)
for obj in objects {
managedContext.delete(obj)
}
do {
try managedContext.save()
} catch {
print("error on delete Team")
}
}
When you save changes in Core Data, the context saves only to its parent context. If it doesn't have a parent context, it saves changes to the persistent store file. You're saving changes on privateManagedObjectContext, which is a child context of viewContext. But you're never saving changes on viewContext. So your child context is telling the parent context about the changes, but the parent never saves those changes anywhere.
You need to either (a) save changes on viewContext, or (b) make privateManagedObjectContext its own stand-alone context, not a child context.
I have opened my realm database in Realm browser, i can see that there is are actual data (10 entities).
But when i call print("realm objects \(self.realm.objects(CharacterModel.self))")
Result is empty:
realm objects Results<CharacterModel> <0x7f8d8f204a30> (
)
When i put breakpoint and check data base state at this moment data exist. Why is that happening?
realm is declared like that:
static func realm() -> Realm{
do {
let realm = try Realm()
return realm
} catch let error as NSError {
fatalError("Error opening realm: \(error)")
}
}
The answer may reveal itself if we eliminate some variables:
The following code works with a Realm that contains Person() objects
func doPrintData() {
do {
let realm = try Realm()
print("realm objects \(realm.objects(Person.self))")
} catch let error as NSError {
print(error.localizedDescription)
}
}
the following also works
func realm() -> Realm{
do {
let realm = try Realm()
return realm
} catch let error as NSError {
fatalError("Error opening realm: \(error)")
}
}
func doPrintData() {
do {
let realm = self.realm()
print("realm objects \(realm.objects(Person.self))")
} catch let error as NSError {
print(error.localizedDescription)
}
}
There's probably more code involved but try one of the above solutions and see if it makes a difference.
I have a function which has multiple queries set up in it. The function is long and involves alot, but works perfectly, save for one issue. There is a query which depends on some information with the query before and it occasionally crashes the app when the query before it is not totally done and caused the second one to unwrap a nil value. Here is the general setup:
func QueryandAppend(completion: (() -> Void)?){
let userLinkQuery = PFUser.query()//query1
userLinkQuery?.findObjectsInBackground(block: { (objects, error) in
if let users = objects{
for object in users{
if let user = object as? PFUser{
userLinkArray[user.objectId!] = user.username
}
}
}
})
let appendingQuery = PFQuery(classname: "Stuff")//query2
appendingQuery.findObjectsInBackground { (objects, error) in
for object in objects{
creatorArray.append(userLinkArray[object["User"] as! String]!)
}
...
completion!()
}
The completion handler is used to make sure the entire function (and all of the queries) have been completed before running something in the viewDidLoad(). How do I ensure, though, that within the function query1 is done before query2 can run?
The prototypical way to do this with network and other asynchronous operations is to chain the operations, perhaps even break the individual network operations up each into a specific routine. The individual operations have completion callbacks, so just invoke your second routine from the completion of the first.
func QueryandAppend(completion: ((Bool) -> Void)?) {
PFUser.query().findObjectsInBackground { (objects, error) in
guard error == nil, let users = objects else {
completion?(false)
return
}
for object in users {
if let user = object as? PFUser,
let objectId = user.objectId {
userLinkArray[objectId] = user.username
}
}
PFQuery(classname: "Stuff").findObjectsInBackground { (objects, error) in
if error != nil {
// Handle error case
completion?(false)
return
}
for object in objects {
creatorArray.append(userLinkArray[object["User"] as! String]!)
}
completion?(true)
}
}
}
Alternatively, for a more modern stylistic approach, you could take a look at a good Promise framework, such as PromiseKit
I'd like to delete databases of realm.
I know how to delete that on Java, but I need to do that on swift
like this(Java):
RealmConfiguration realmConfig = new RealmConfiguration.Builder(this).build();
Realm.deleteRealm(realmConfig);
realm = Realm.getInstance(realmConfig);
thanks.
If you want to delete Realm's files
let manager = NSFileManager.defaultManager()
let realmPath = Realm.Configuration.defaultConfiguration.path as! NSString
let realmPaths = [
realmPath as String,
realmPath.stringByAppendingPathExtension("lock")!,
realmPath.stringByAppendingPathExtension("log_a")!,
realmPath.stringByAppendingPathExtension("log_b")!,
realmPath.stringByAppendingPathExtension("note")!
]
for path in realmPaths {
do {
try manager.removeItemAtPath(path)
} catch {
// handle error
}
}
From Realm's official documentation: https://realm.io/docs/swift/latest/#deleting-realm-files
Best thing to do is to call deleteAll() method on realm object like:
let realm = try! Realm()
try! realm.write {
realm.deleteAll()
}
I have been struggling with this problem for exactly 8 days now. So I believe it's time to ask for help for the Gurus over here.
Ok, so I am trying to implement an app following Uncle Bob's Clean Architecture, so I have the ViewControllers, Models, Interactors, Repositories and Presenters all setup.
My AppDelegate does not have any traces of CoreData in it, nothing. All that is done in the MyAppCoreData class.
The repository is injected in the AppDelegate and the Interactor access the injected object to access not only CoreData stuff, but also Parse and another private API. The Parse and private API are working great.
The CoreData repository also "works". It does not throw any exception. But no data is inserted in CoreData and when I fetch, it's empty.
I believe I am having some problem with the Persistent Store Coordinator + Main Context + Private Context... but have not yet managed to find out what it is.
So let's go to the code:
AppDelegate
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let container = Container() { c in
c.register(UserDataStore.self) { _ in ParseRepository() }
c.register(BabyNamesDataStore.self) { _ in BabyNameRepository.sharedInstance }
c.register(GenericRepository.self) { r in
GenericRepository(_userDataStore: r.resolve(UserDataStore.self)!, _babyNameDataStore: r.resolve(BabyNamesDataStore.self)!)
}
}
...
Then, my interactors have the following init() function:
class MainInteractor: MainInteractorInput
{
var output: MainInteractorOutput!
var worker: GenericRepository?
init() {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
worker = appDelegate.container.resolve(GenericRepository.self)
}
Then, finally, my CoreDataStore is a singleton (before it wasn't and I could see that it was being instantiated twice for some reason by Swinject):
class BabyNamesCoreDataStore : BabyNamesDataStore{
var mainManagedObjectContext: NSManagedObjectContext
var privateManagedObjectContext: NSManagedObjectContext
static let sharedInstance = BabyNamesCoreDataStore()
init()
{
QL1("BabyNamesCoreDataStore init")
// This resource is the same name as your xcdatamodeld contained in your project.
guard let modelURL = NSBundle.mainBundle().URLForResource("BabyNameMe", withExtension:"momd") else {
fatalError("Error loading model from bundle")
}
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
guard let mom = NSManagedObjectModel(contentsOfURL: modelURL) else {
fatalError("Error initializing mom from: \(modelURL)")
}
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
mainManagedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
mainManagedObjectContext.persistentStoreCoordinator = psc
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
let docURL = urls[urls.endIndex-1]
/* The directory the application uses to store the Core Data store file.
This code uses a file named "DataModel.sqlite" in the application's documents directory.
*/
let storeURL = docURL.URLByAppendingPathComponent("BabyNameMe.sqlite")
do {
try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil)
} catch let error as NSError {
QL4("Could not save \(error), \(error.userInfo)")
fatalError("Error migrating store: \(error)")
}
//let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateManagedObjectContext.parentContext = mainManagedObjectContext
}
deinit
{
do {
try self.mainManagedObjectContext.save()
} catch {
fatalError("Error deinitializing main managed object context")
}
}
func fetchBabyNames(completionHandler: (babyNames: [BabyNames], error: StoreError?) -> Void) {
privateManagedObjectContext.performBlock {
do {
let fetchRequest = NSFetchRequest(entityName: "ManagedBabyNames")
let predicate = NSPredicate(format: "liked == nil")
fetchRequest.predicate = predicate
let results = try self.privateManagedObjectContext.executeFetchRequest(fetchRequest) as! [ManagedBabyNames]
let result = results.map { $0.toBabyName() }
completionHandler(babyNames: result, error: nil)
} catch let error as NSError {
QL4("Could not save \(error), \(error.userInfo)")
completionHandler(babyNames: [], error: StoreError.CannotFetch("Cannot fetch baby names"))
}
}
}
func insertBabyNames(babyNames: BabyNames, completionHandler: (error: StoreError?) -> Void) {
privateManagedObjectContext.performBlock {
do {
let managedBabyNames = NSEntityDescription.insertNewObjectForEntityForName("ManagedBabyNames", inManagedObjectContext: self.privateManagedObjectContext) as! ManagedBabyNames
managedBabyNames.fromBabyName(babyNames)
try self.privateManagedObjectContext.save()
completionHandler(error: nil)
} catch let error as NSError {
QL4("Could not save \(error), \(error.userInfo)")
completionHandler (error: StoreError.CannotCreate("Cannot create baby names with id \(babyNames.id)"))
}
}
}
...
And this is pretty much it. I get no exceptions but it is simply not working.
Could anyone please help this desperate man ? :)
Thanks!
UPDATE
I am following Raymund's Clean Store idea to implement my own, found here: https://github.com/Clean-Swift/CleanStore
I found out that the function that receives the response from the Presenter, inside the View Controller is NOT in the main thread. So I had to add this:
func displaySomething(viewModel: LoginViewModel) {
if viewModel.loginStatus {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.prepareSlideOutVC()
//When user is not logged in, the SlideOut Menu is not loaded. So when he logs in, we must load it.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
dispatch_async(dispatch_get_main_queue()) {
self.router.navigateToMainScene()
}
}
}
}
adding the dispatch_async sorted out the problem in a few cases, but not all of them... still trying to figure out.
So, basically, after I login and receive the response from the Presenter, I activate the segue to go to MainVC but that was in a different thread.
Still trying to work this out.