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.
Related
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)
}
}
}
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'm new to Swift Realm. Here's my method to save single object.
let realm = try! Realm()
func savePerson(_ person: Person){
do{
try realm.write {
realm.add(person)
}
}catch{
print(error.localizedDescription)
}
}
Is there any method to save Array?
func savePersonList(_ personList: []){
do{
try realm.write{
// HOW??
}
}catch{
}
}
I have an application using Core Data as its database, I'm trying to write test cases for my DB queries which I have written inside my NSManagedObject subclass and they will return true if the insertion works successfully. I'm trying to insert an object into my entity like this:
func testDBInser() {
let appDelegate: AppDelegate = AppDelegate()
let managedObjectContext = appDelegate.persistentContainer.viewContext
let myObject: MyManagedObject = MyManagedObject(context: managedObjectContext)
myObject.id = 10
myObject.name = "testObj"
XCTAssertTrue(UserProfileModel().insertObjectToUserProfile(myObject))
}
But I'm getting this error for my test:
An NSManagedObject may only be in (or observed by) a single NSManagedObjectContext
Life is a lot easier with CoreStore:
func testInsert {
// 1. Arrange
let dataStack: DataStack = {
let dataStack = DataStack(xcodeModelName: "ModelName")
do {
try dataStack.addStorageAndWait()
} catch let error {
XCTFail("Cannot set up database storage: \(error)")
}
return dataStack
}()
// 2. Action
do {
try dataStack.perform(synchronous: { transaction in
let object = transaction.create(Into<TestObject>())
object.name = "Test"
})
} catch let error {
XCTFail("Cannot perform database transaction: \(error)")
}
// 3. Assert
do {
try dataStack.perform(synchronous: { transaction in
guard transaction.fetchOne(From<TestObject>(), Where("name", isEqualTo: "Test")) != nil else {
XCTFail("Cannot get database object")
}
})
} catch let error {
XCTFail("Cannot perform database transaction: \(error)")
}
}
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.