How to access a realm database from multiple controllers - swift - ios

I am attempting to make a to-do app with a controller for both "this week" and "next week." (Objects with a date in the current week will be shown in the thisWeek view controller, while objects with a date next week will be shown in the nextWeek view controller. However, sometimes objects will not appear in the table until the app restarts, and I'm not sure why this is happening or how to fix it.
In the thisWeek class (which is also my initial view controller), I initialize my database like so:
let config = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
if oldSchemaVersion < 1 {
migration.enumerateObjects(ofType: ToDoListItem.className()) { (_, newItem) in
newItem?["notes"] = ""
newItem?["isReoccuring"] = false
}
}
})
lazy var realm = try! Realm(configuration: config)
This worked fine and there weren't any issues. However, I don't know how best to access this db from another controller (nextWeek). This was my first attempt: (which is what I also use in the object entry controller, where objects are first added to the db)
lazy var realm = try! Realm()
I also added a refresh() to each controller's viewDidLoad() with the intention of refreshing the table every time the view is loaded. But it didn't help every situation.
func refresh(){
data = sortContent(arg: realm.objects(ToDoListItem.self).map({ $0 }))
thisTable.reloadData()
}
The issue with this is that some objects do not appear in their proper place until I restart the app. Only then do all objects appear properly. For example:
something created in the nextWeek controller with a date in the current week will not appear until app restarts
when an object in created in nextWeek controller, then date is changed from next week to this week, again, needs an app restart
Things that do work include:
something created in the thisWeek controller with a date in the next week will appear immediately
when an object is created in thisWeek controller, then date is changed from this week to next week works immediately
I thought I needed a way to sync the different "instances" of the db, but there may be another issue that I just don't know about. Any insight would be helpful
Would be happy to share more code snippets if more information is needed.
UPDATE
This is in AppDelegate.swift:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let config = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
if oldSchemaVersion < 1 {
migration.enumerateObjects(ofType: ToDoListItem.className()) { (_, newItem) in
newItem?["notes"] = ""
newItem?["isReoccuring"] = false
}
}
})
Realm.Configuration.defaultConfiguration = config
let realm = try! Realm()
return true
}
And everywhere I need to use realm (thisWeek and nextWeek controllers, etc), I use:
let realm = RealmService.shared.realm
Again, only certain functionalities are properly working

First the code in the section I initialize my database initializes that database var with a config.
lazy var realm = try! Realm(configuration: config)
The next section could be a 'different' realm as no config is specified
lazy var realm = try! Realm()
Please review Local Migrations, noting that a migration should occur when the app is first launched, not every time realm is accessed
Inside your application(application:didFinishLaunchingWithOptions:)
In other words, perform the migration on app launch (ensuring to include Realm.Configuration.defaultConfiguration = config) and then after that use
let realm = try! Realm()
to access a local realm.
You will probably not want to do this:
data = sortContent(arg: realm.objects(ToDoListItem.self).map({ $0 }))
Realm objects are live updating and that code casts the realm objects to an array which not only 'breaks' the link between the objects and their backing data, it also loads ALL of the objects into memory, defeating their lazily loading nature. This can overwhelm a device and cause a crash. See Realm Key Concepts and Read Characteristics
To answer the question;
I thought I needed a way to sync the different "instances" of the db
You don't. You should access a realm the same way every time. Objects created on different realms are tied to that realm and trying to access them from a different realm will cause a crash.
One solution is to use a Singleton pattern, sometimes called a RealmManager or RealmService (search here on SO for those key words). While there is some debate about singletons in Swift, it works for this case.
Another solution is to have a master controller which 'talks' to realm and then sub viewControllers get their connection to realm from the master viewController. The would be a simple matter of instantiating the sub viewController from the master viewController and passing and/or assigning a property in the sub viewController. So conceptually something like this.
MasterVC
func getRealm() -> Realm {
//return a realm instance
}
func createSubVC {
let mySubVC = SubViewController()
mySubVC.realm = gGetRealm()
}
But for simplicity and maintenance, I would suggest a singleton pattern for this use case. You could do something like this; create a file and put this code in it
import RealmSwift
func gGetRealm() -> Realm? {
do {
let realm = try Realm()
return realm
} catch let error as NSError {
print("Error!")
print(" " + error.localizedDescription)
let err = error.code
print(err)
let t = type(of: err)
print(t)
return nil
}
}
then whenever you need a realm instance in your app
func saveData() {
if let realm = gGetRealm() {
try! realm.write {
//save some data
}
}
or a RealmService singleton
import RealmSwift
class RealmService {
private init() {}
static let shared = RealmService()
lazy var realm: Realm = {
let realm = try! Realm()
return realm
}()
}
accessed like this
let realm = RealmService.shared.realm

Related

Back button and creating new entry causes app to crash (UUID cannot be amended) Realm Swift

When I go back to VC1 (which allows the user to input a title for a book and create an entry in realm including the title and a UUID for that book) from VC2 (using the provided back button as part of the navigation controller not a custom segue) and then create a new book object in Realm (by adding another title to the text field in VC1), the app crashes saying I cannot amend the primary key once set.
I am intending to create a new entry (in theory I could add one, go back, add another etc) rather than try to overwrite an existing entry.
I've read the docs (and even looked at an old project where a similar thing is working) but I can't understand why it isn't just creating a new entry. I looked at the Realm docs (e.g. referenced in this answer Realm in IOS: Primary key can't be changed after an object is inserted)
Code here is VC1 allowing the user to create a new novel (by adding a title into a text field which is earlier in the code)
func createNewNovel() {
let realm = try! Realm()
novelCreated.novelID = UUID().uuidString
novelCreated.novelTitle = novelTitleInput.text!
novelCreated.createdDate = Date()
do {
try realm.write {
realm.add(novelCreated)
print ("Novel Created Successfully")
}
} catch {
print("error adding novel")
}
Then I prepare to pass the novelID to VC2 :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let novelData = NovelObject()
novelData.novelID = novelCreated.novelID
if let destVC = segue.destination as? WriteVC {
destVC.novelIDPassed = novelData.novelID
}
}
This works fine and the segue triggers from a button press on VC1. I can also print the ID of the novel in VC2 so that's working fine.
BUT... when I go back from VC2 and input a new title in the text field in VC1 it should create a new NovelObject, not try to update the existing one (which is causing it to crash). You could even theoretically have the same book title twice but they should have a different UUID (which is the primary key)
I must be missing something trivial I suppose but can't work it out!
The novel is created as at the top :
class NewNovelVC: UIViewController {
let novelCreated = NovelObject()
#IBOutlet weak var novelTitleInput: UITextField!
#IBOutlet weak var createButtonOutlet: UIButton!
then it is populated with variables
This is due to classic issue of same object being in the memory re-instantiated which points to the same value of primary key. Creating a singleton class can be very handy here as well.
Create a service file. This will keep your RealSwift initialized on a specific thread as your current block.
RealService.swift
import RealmSwift
class RealmService {
static let uirealm = RealmService()
private var _initRS = try! Realm()
var realm: Realm! {
return _initRS
}
}
Novel.swift :
import RealmSwift
class Novel : Object {
#objc dynamic var uid : String? = nil
#objc dynamic var title: String? = nil
override static func primaryKey() -> String {
return "uid"
}
}
extension Novel {
func writeToRealm(){
try? RealmService.uirealm.realm.write {
print("Creating the Novel Object")
RealmService.uirealm.realm.add(self, update: true)
}
}
func DeleteFromRealm(object: Results<Novel>){
try? RealmService.uirealm.realm.write {
print("Deleting the Novel Object")
RealmService.uirealm.realm.delete(object)
}
}
}
and just implement as #Don mentioned, write in the block where you are setting the title.
var novel: Novel! // Declare this in above in the class.
novel = Novel()
novel.title = title
novel.uid = uid
novel.writeToRealm()
Hope that helped.

Managed Object Context is nil for some reason in iOS

I'm using Alamofire to submit a request to an endpoint using Swift. I parse the JSON objects that I receive from the response using the Codable protocol, and then try to insert the objects into Core Data using Managed Object Subclasses. However, when I do this, I keep receiving an error saying that my parent Managed Object Context (MOC) is nil. This doesn't make sense to me because I set the MOC via Dependency Injection from the AppDelegate, and confirm that it has a value by printing out it's value to the console in the viewDidLoad() method.
Here is my relevant code:
I set my MOC here:
class ViewController: UIViewController {
var managedObjectContext: NSManagedObjectContext! {
didSet {
print("moc set")
}
}
override func viewDidLoad() {
super.viewDidLoad()
print(managedObjectContext)
}
///
func registerUser(userID: String, password: String) {
let parameters: [String: Any] = ["email": userID, "password": password, "domain_id": 1]
let headers: HTTPHeaders = ["Accept": "application/json"]
Alamofire.request(registerURL, method: .patch, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseJSON { response in
switch response.result {
case .success:
if let value = response.result.value {
print(value)
let jsonDecoder = JSONDecoder()
do {
let jsonData = try jsonDecoder.decode(JSONData.self, from: response.data!)
print(jsonData.data.userName)
print(jsonData.data.identifier)
print(self.managedObjectContext)
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = self.managedObjectContext
let user = UserLogin(context: privateContext)
user.userName = jsonData.data.userName
user.domainID = Int16(jsonData.data.identifier)
user.password = "blah"
do {
try privateContext.save()
try privateContext.parent?.save()
} catch let saveErr {
print("Failed to save user", saveErr)
}
} catch let jsonDecodeErr{
print("Failed to decode", jsonDecodeErr)
}
}
case .failure(let error):
print(error)
}
}
}
The specific error message I'm getting is:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Parent NSManagedObjectContext must not be nil.'
I realize that Alamofire is download the data on a background thread, which is why I use a child context, but I'm not sure why the parent is nil.
Here is the setup code for my Managed Object Context:
class AppDelegate: UIResponder, UIApplicationDelegate {
var persistentContainer: NSPersistentContainer!
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
createContainer { container in
self.persistentContainer = container
let storyboard = self.window?.rootViewController?.storyboard
guard let vc = storyboard?.instantiateViewController(withIdentifier: "RootViewController") as? ViewController else { fatalError("Cannot instantiate root view controller")}
vc.managedObjectContext = container.viewContext
self.window?.rootViewController = vc
}
return true
}
func createContainer(completion: #escaping(NSPersistentContainer) -> ()) {
let container = NSPersistentContainer(name: "Test")
container.loadPersistentStores { _, error in
guard error == nil else { fatalError("Failed to load store: \(error!)") }
DispatchQueue.main.async { completion(container) }
}
}
Can anyone see what it is I'm doing wrong?
I don't see anything immediately "wrong" so lets debug this a bit.
Put a breakpoint in applicationDidFinish... right after the guard.
Put a breakpoint at the creation of the privateContext.
Which fires first?
Where is the registerUser function? In a view controller? I hope not :)
the breakpoint right after my guard statement fires first. And yes, my registerUser function is indeed inside a ViewController.
Putting network code in view controllers is a code smell. View Controllers have one job, manage their views. Data gathering belongs in a persistence controller; for example, extending your NSPersistentContainer and putting data collection code there.
However, that is not the issue here, just a code smell.
Next test.
Is your persistent container and/or viewContext being passed into your view controller and bring retained?
Is your view controller being destroyed before the block fires?
To test this, I would put an assertion before Alamofire.request and crash if the context is nil:
NSAssert(self.managedObjectContext != nil, #"Main context is nil in the view controller");
I would also put that same line of code just before:
privateContext.parent = self.managedObjectContext
Run again. What happens?
I ran the test as you described, and I get the error: Thread 1: Assertion failed: Main context is nil in the view controller
Which assertion crashed? (probably should change the text a bit...)
If it is the first one then your view controller is not receiving the viewContext.
If it is the second one then the viewContext is going back to nil before the block executes.
Change your assumptions accordingly.
discovered something that is relevant here: If I place a button to call the registerUser() function at the user's discretion instead of calling it directly from the viewDidLoad() method, there is no crash, the code runs fine, and the MOC has a value
That leads me down the theory that your registerUser() was being called before your viewDidLoad(). You can test that by putting a break point in both and see which one fires first. If your registerUser() fires first, look at the stack and see what is calling it.
If it fires after your viewDidLoad() then put a breakpoint on the context property and see what is setting it back to nil.
So if I remove that line, how do I set the MOC property on my RootViewController via Dependency Injection?
The line right before it is the clue here.
let storyboard = self.window?.rootViewController?.storyboard
Here you are getting a reference to the storyboard from the rootViewController which is already instantiated and associated with the window of your application.
Therefore you could change the logic to:
(self.window?.rootViewController as? ViewController).managedObjectContext = container.viewContext
Although I would clean that up and put some nil logic around it :)
The problem I realize is that the MOC in the RootViewController is being used BEFORE the MOC is returned from the closure, and set in the AppDelegate. What do I do here?
This is a common synchronous (UI) vs. asynchronous (persistence) issue. Ideally your UI should wait until the persistence loads. If you load the persistent stores and then finish the UI after the stores have loaded it will resolve this issue.
Without a migration we are generally talking ms here rather than seconds.
BUT ... You want the same code to handle the UI loading whether it is milliseconds or seconds. How you solve that is up to you (design decision). One example is to continue the loading view until the persistence layer is ready and then transition over.
If you did that you could then subtly change the loading view if a migration is happening to inform the user as to why things are taking so long.

Data not persistent in Core Data when app re-launches

I am using Core Data for the first time in a project made in XCode 8, swift 3. I have used background context (calling container.performBackgroundTask block..) to save the data and main context to fetch the data. When my app re-launches, the data I saved in the private background context is deleted.
Please tell me where I went wrong !!!
Here I call CoreDataManager class save context method in applicationDidEnterBackground and applicationWillTerminate methods of AppDelegate class:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
lazy var coreDataMgrInstance = CoreDataManager.sharedInstance
func applicationDidEnterBackground(_ application: UIApplication) {
coreDataMgrInstance.saveContext()
}
func applicationWillTerminate(_ application: UIApplication) {
coreDataMgrInstance.saveContext()
}}
Here is my Singleton class CoreDataManager to initiate NSpersistentContainer
class CoreDataManager: NSObject {
class var sharedInstance: CoreDataManager {
struct Singleton {
static let instance = CoreDataManager()
}
return Singleton.instance
}
private override init() {
super.init()
}
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "E_CareV2")
let description = NSPersistentStoreDescription() // enable auto lightweight migratn
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions = [description]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
func saveContext(){
print("saveContext")
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Failure to save main context \(nserror), \(nserror.userInfo)")
}
}}
Now this is the class where I save and fetch the data from Core Data
class SenderViewController: UIViewController {
var persistentContainer: NSPersistentContainer!
override func viewDidLoad() {
super.viewDidLoad()
persistentContainer = CoreDataManager.sharedInstance.persistentContainer
let results = self.fetchPersistentData(entityName: "School", withPredicate: nil)
print("results \n \(results)")
}
#IBAction func enterPressed(_ sender: Any) {
self.persistentContainer.performBackgroundTask({ (backgroundContext) in
backgroundContext.mergePolicy = NSMergePolicy.mergeByPropertyStoreTrump
let schoolentity = NSEntityDescription.insertNewObject(forEntityName: "School", into: backgroundContext) as! School
schoolentity.schoolName = "ABC"
schoolentity.schoolWebSite = "http://anywebsite.com/"
do {
try backgroundContext.save()
} catch {
fatalError("Failure to save background context: \(error)")
}
})
}
func fetchPersistentData(entityName: String?, withPredicate: NSPredicate?) -> [NSManagedObject]{
let context = self.persistentContainer.viewContext
let request: NSFetchRequest<School> = School.fetchRequest()
let newentity = NSEntityDescription.entity(forEntityName: entityName!, in: context)
request.entity = newentity
request.returnsObjectsAsFaults = false
do {
let searchResults = try context.fetch(request) as [NSManagedObject]
return searchResults
} catch {
print("Error with request: \(error)")
}
return []
}
Actually the lightweight migrations are enabled by default as you can see on the screenshot
So you can safely delete these lines:
let description = NSPersistentStoreDescription() // enable auto lightweight migratn
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions = [description]
After that everything should work.
There are two ways to use NSPersistentContainer - the simple way and the correct way. You are mixing them which is leading to problems. Apple is a little inconsistent on this in its documentation so it is understandable that you are confused.
The Simple Way - The simple way is only use the viewContext both for reading and for writing and only from the main thread. There are never any conflicts because core-data is assessed via a single thread. The problem with this approach is that you can't run anything in a background thread. So if you are importing a lot of data the UI will freeze. If you have a super simple app (a small task list?) this will work OK, but not something that I would ever recommend for a serious app. It is OK for a testing app for a beginner to learn core-data.
The Correct Way - the correct way is to NEVER write to the viewContext EVER. Apple documents this in NSPersistentContainer documentation (but also in its template creates a save method for the viewContext?!). In this setup all writes to core data MUST go through performBackgroundTask and you have to call save on that context before the end of the block. You also need an operation queue to make sure that there are no merge conflicts see NSPersistentContainer concurrency for saving to core data. This setup is a lot harder to do correctly. Objects from performBackgroundTask contexts cannot leave the block and objects from the viewContext cannot be used in the block. The complier doesn't help you with this so it is something that you always need to watch out for.
Your problem is mergePolicy. mergePolicy is evil. If you have conflicts in core-data you have already done something wrong and any merge policy will lose data. In the simple setup there are no conflicts because it is all on one thread. In the correct setup there is no conflicts because of the queue you created when using performBackgroundTask. The problem is that if you use BOTH performBackgroundTask and write the the viewContext you can get conflicts and you will lose data. Personally, I think it is better to have no mergePolicy and crash then to silently lose data.
I figured out what causes my data not to be persistent in Core Data. It was these below 4 lines of code that I put in persistentContainer definition for enabling LIGHTWEIGHT MIGRATION of models:
let description = NSPersistentStoreDescription() // enable auto lightweight migratn
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions = [description]
When I deleted these lines, I became able to retain my data when the app re-launches.
I had written the above lines to enable Lightweight Migration to my model, but I did not change my model or created new version of the model, making the Core Data unable to search the destination model in NSBundle and thus unable to infer the Mapping.
I am still not sure, how that would delete my data but I will keep trying to figure this out too and comment when I get success in it... :)

Updating label if value in singleton changes

I am getting some user information from Firebase and storing it into singleton. After that every time the value changes I want that the label changes also but it doesn't until I terminate the app and come back in.
How should I update label if value changes in singleton?
I have tab views. In first tab I assign values and in second tab I try to put the values to label.
This is my singleton:
class CurrentUser: NSObject
{
var generalDetails: User = User()/// Consecutive declarations on a line must be separated by ';'
static let sharedInstance = CurrentUser()
fileprivate override init(){
super.init()
}
}
And like this I assign values:
self.databaseRef.child("users").child(user.uid).observeSingleEvent(of: .value) { (snapshot:FIRDataSnapshot) in
guard let firebaseValue = snapshot.value as? [String:AnyObject], let userName = firebaseValue["username"] as? String, let email = firebaseValue["email"] as? String, let reputation = firebaseValue["reputation"] as? Int, let profilePicURL = firebaseValue["profileImageUrl"] as? String
else
{
print("Error with FrB snapshot")//Here
return
}
//Set values
self.currentUser.generalDetails = User(userName: userName, email: email, reputation: reputation, profileImageURL: profilePicURL, uid: user.uid)
}
And if I want to put the value to the label I simply do this(This reputation is the only thing that can change often):
self.reputationLabel.text = String(self.currentUser.generalDetails.reputation)
You can do either of these:-
Communicate between the singleton and your class with delegate-protocol method , fire the delegate method in the class whenever your repo changes and update your label.
Open a different thread in your network link for the user's reputation in the viewController itself:-
override func viewDidLoad() {
super.viewDidLoad()
FIRDatabase.database().reference().child("users").child(FIRAuth.auth()!.currentUser!.uid).child("reputation").observe(.childChanged, with: {(Snapshot) in
print(Snapshot.value!)
//Update yor label
})
which will get called every time the value of reputation changes.
I like Dravidian's answer and I would like to offer an alternative: KVO
We use Key-Value Observing to monitor if our app is disconnected from the Firebase server. Here's the overview.
We have a singleton which stores a boolean variable isConnected, and that variable is set by observing a special node in Firebase
var isConnected = rootRef.child(".info/connected")
When connected/disconnected, the isConnected var changes state.
We have a little icon on our views that indicates to the user the connected state; when connected it's green, when disconnected it's red with a line through it.
That icon is a class and within each class we have code that observes the isConnected variable; when it's state changes all of the icons change automatically.
It takes very little code, is reusable, is clean and easily maintained.
Here's a code snippet from the Apple documentation
//define a class that you want to observe
class MyObjectToObserve: NSObject {
dynamic var myDate = NSDate()
func updateDate() {
myDate = NSDate()
}
}
//Create a global context variable.
private var myContext = 0
//create a class that observes the myDate var
// and will be notified when that var changes
class MyObserver: NSObject {
var objectToObserve = MyObjectToObserve()
objectToObserve.addObserver(self,
forKeyPath: "myDate",
options: .new,
context: &myContext)
There will be more to it but that's it at a 10k foot level.
The Apple documentation is here
Using Swift with Cocoa and Obj-c 3.01: Design Patterns
and scroll down the the Key-Value Observing Section. It's a good read and very powerful. It follows the same design pattern as Firebase (or vice-versa) - observe a node (variable) and tell me when it changes.

Realm not working after migration

This is the relevant code in my UIViewController:
class HabitTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
#IBOutlet weak var habitTableView: UITableView!
private var _numOfRowsInSects: [Int] = []
private var _allSections = Set<Int>() //_[0] = 1 -> Morning
private let _timeInDay = [0: "Morning", 1: "Afternoon", 2:"Evening", 3:"Anytime"]
private var _habitsBySection:[[Habit]] = []
private var _whatIsToday = -1 //means no button other than today has been pressed
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
habitTableView.delegate = self
habitTableView.dataSource = self
var error: NSError?
NSFileManager.defaultManager().removeItemAtPath(Realm.defaultPath, error:&error)
let realm = Realm()
//for testing purposes, preload some habits
let habit1 = Habit()
let habit2 = Habit()
let habit3 = Habit()
let habit4 = Habit()
//set up code -- assigning properties and etc.
realm.write{realm.add(habit1)}
realm.write{realm.add(habit2)}
realm.write{realm.add(habit3)}
realm.write{realm.add(habit4)}
}
#IBAction func reloadTableForDay(sender: DayButton){
if sender.tag != getDayOfWeek(-1){
_whatIsToday = sender.tag
_habitsBySection = []
_allSections = []
habitTableView.reloadData()
}
else{
_whatIsToday = -1
}
}
func getHabitsForDay(daySelected: Int) -> Results<Habit> {
let daySelected = String(daySelected)
let habitsOfDay = Realm().objects(Habit).filter("durationByDay_days contains %#", "7")
return habitsOfDay
}
}
I set up the data to be persisted in viewDidLoad(), for testing purposes. However my getHabitsForDay(daySelected: Int) function only returns query result when the program first runs, i.e. when I click the buttons that call the reloadTableForDay(sender: DayButton) function, which in turn calls reload to the UITable, nothing happens and in my console I can see the query returned an empty Result<Habit>. This all happened after I changed my data model (added a property and a class) and performed the migration.
I also suspect that
var error: NSError?
NSFileManager.defaultManager().removeItemAtPath(Realm.defaultPath, error:&error)
could be messing things up, but I'm not sure.
EDIT: Now i'm sure this was caused by migration, as I started a new project and copied over the code. Everything was working fine until I did a migration.
This is the migration code in my AppDelegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let config = Realm.Configuration(
//You need to increment the version everytime you change your object schema (starts at 0)
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
//If you want to preserve any data, you can do it here, otherwise just leave it blank.
}
)
Realm.Configuration.defaultConfiguration = config
let realm = Realm()
return true
}
I agree that the line of code in your view controller where you're deleting the Realm file would most likely be the cause of the problem. If you want to delete the default Realm file, it would be much safer to do it before Realm() is called anywhere for the first time.
Realm retains references to itself in memory (So it doesn't need to continually set itself up each time you call Realm() on separate threads), so I'd say it's safe to assume that it's state in memory might be getting confused with the file getting deleted after it was already opened.
If you're deleting the file, simply for testing reasons, I'd recommend deleting it before you set the migration block and call Realm() for the first time.

Resources