Originally created by @jamesbebbington on GitHub (Feb 8, 2016).
Having implemented a sectioned ListMonitor as the datasource for my controller, I'm finding that it only returns records when the underlying store content changes.
I boot my app, insert data into the store and the table view is updated accordingly. But when I restart the app, the table view is empty. However, a CoreStore.fetchAll reveals that there is data in the store.
Am I doing anything obviously wrong?
importUIKitimportCoreStoreclassProjectsController:UITableViewController,ListSectionObserver,TableViewDisabling{// MARK: PropertiesletappDelegate=UIApplication.sharedApplication().delegateas!AppDelegateenumSegue:String{caseShowProject}letprojects:ListMonitor<Project>={returnCoreStore.monitorSectionedList(From(Project),SectionBy("state"){(sectionName)->String?insectionName?.capitalizedString},Where(true),OrderBy(.Ascending("state"),.Ascending("name")))}()// MARK: NSObjectdeinit{projects.removeObserver(self)}// MARK: UIViewControlleroverridefuncviewDidLoad(){super.viewDidLoad()projects.addObserver(self)self.refreshControl?.addTarget(self,action:"refresh:",forControlEvents:UIControlEvents.ValueChanged)appDelegate.syncManager.onComplete.listen(self){// N.B. Refresh control can cause UI test weirdness// See: https://forums.developer.apple.com/thread/6503ifletrefreshControl=self.refreshControlwhererefreshControl.refreshing{refreshControl.endRefreshing()}}// Prints records to the console as expected…print("projects",CoreStore.fetchAll(From(Project)))}overridefuncviewWillAppear(animated:Bool){super.viewWillAppear(animated)// Deselect row on Back gesture. See: http://stackoverflow.com/a/23895557/171144ifletindexPath=tableView.indexPathForSelectedRow{tableView.deselectRowAtIndexPath(indexPath,animated:animated)}}overridefuncprepareForSegue(segue:UIStoryboardSegue,sender:AnyObject?){guardletidentifier=Segue(rawValue:segue.identifier!)else{return}switchidentifier{case.ShowProject:guardletindexPath=tableView.indexPathForSelectedRowelse{return}letdestination=segue.destinationViewControlleras!ProjectControllerletproject=projects[indexPath]destination.project=project}}// MARK: UITableViewControllerfuncrefresh(sender:AnyObject){ifletrootController=self.parentViewControlleras?RootController{rootController.sync()}}// MARK: UITableViewDataSourceoverridefuncnumberOfSectionsInTableView(tableView:UITableView)->Int{returnprojects.numberOfSections()}overridefunctableView(tableView:UITableView,numberOfRowsInSectionsection:Int)->Int{returnprojects.numberOfObjectsInSection(section)}// Section headersoverridefunctableView(tableView:UITableView,titleForHeaderInSectionsection:Int)->String?{letsectionInfo=self.projects.sectionInfoAtIndex(section)returnsectionInfo.name}// Project cellsoverridefunctableView(tableView:UITableView,cellForRowAtIndexPathindexPath:NSIndexPath)->UITableViewCell{letcell=tableView.dequeueReusableCellWithIdentifier("ProjectCell",forIndexPath:indexPath)asUITableViewCellletproject=projects[indexPath]cell.textLabel!.text=project.namereturncell}overridefuncsectionIndexTitlesForTableView(tableView:UITableView)->[String]?{// Don't display section index letters down right-hand side of tablereturnnil}// MARK: ListObserverfunclistMonitorWillChange(monitor:ListMonitor<Project>){self.tableView.beginUpdates()}funclistMonitorDidChange(monitor:ListMonitor<Project>){self.tableView.endUpdates()}funclistMonitorWillRefetch(monitor:ListMonitor<Project>){self.setTableEnabled(false)}funclistMonitorDidRefetch(monitor:ListMonitor<Project>){self.tableView.reloadData()self.setTableEnabled(true)}// MARK: ListObjectObserverfunclistMonitor(monitor:ListMonitor<Project>,didInsertObjectobject:Project,toIndexPathindexPath:NSIndexPath){self.tableView.insertRowsAtIndexPaths([indexPath],withRowAnimation:.Automatic)}funclistMonitor(monitor:ListMonitor<Project>,didDeleteObjectobject:Project,fromIndexPathindexPath:NSIndexPath){self.tableView.deleteRowsAtIndexPaths([indexPath],withRowAnimation:.Automatic)}funclistMonitor(monitor:ListMonitor<Project>,didUpdateObjectobject:Project,atIndexPathindexPath:NSIndexPath){ifletcell=self.tableView.cellForRowAtIndexPath(indexPath){letproject=projects[indexPath]cell.textLabel!.text=project.name}}funclistMonitor(monitor:ListMonitor<Project>,didMoveObjectobject:Project,fromIndexPath:NSIndexPath,toIndexPath:NSIndexPath){self.tableView.deleteRowsAtIndexPaths([fromIndexPath],withRowAnimation:.Automatic)self.tableView.insertRowsAtIndexPaths([toIndexPath],withRowAnimation:.Automatic)}// MARK: ListSectionObserverfunclistMonitor(monitor:ListMonitor<Project>,didInsertSectionsectionInfo:NSFetchedResultsSectionInfo,toSectionIndexsectionIndex:Int){self.tableView.insertSections(NSIndexSet(index:sectionIndex),withRowAnimation:.Automatic)}funclistMonitor(monitor:ListMonitor<Project>,didDeleteSectionsectionInfo:NSFetchedResultsSectionInfo,fromSectionIndexsectionIndex:Int){self.tableView.deleteSections(NSIndexSet(index:sectionIndex),withRowAnimation:.Automatic)}}
Incidentally, the ListObserver demos in the CoreStoreDemo app initially appear empty too, which was unexpected (are they meant to start empty?) and led me to wonder if CoreStore might be at fault:
Thanks.
Originally created by @jamesbebbington on GitHub (Feb 8, 2016).
Having implemented a sectioned `ListMonitor` as the datasource for my controller, I'm finding that it only returns records when the underlying store content changes.
I boot my app, insert data into the store and the table view is updated accordingly. But when I restart the app, the table view is empty. However, a `CoreStore.fetchAll` reveals that there is data in the store.
Am I doing anything obviously wrong?
``` swift
import UIKit
import CoreStore
class ProjectsController: UITableViewController, ListSectionObserver, TableViewDisabling {
// MARK: Properties
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
enum Segue: String {
case ShowProject
}
let projects: ListMonitor<Project> = {
return CoreStore.monitorSectionedList(
From(Project),
SectionBy("state") { (sectionName) -> String? in
sectionName?.capitalizedString
},
Where(true),
OrderBy(.Ascending("state"), .Ascending("name"))
)
}()
// MARK: NSObject
deinit {
projects.removeObserver(self)
}
// MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
projects.addObserver(self)
self.refreshControl?.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
appDelegate.syncManager.onComplete.listen(self) {
// N.B. Refresh control can cause UI test weirdness
// See: https://forums.developer.apple.com/thread/6503
if let refreshControl = self.refreshControl where refreshControl.refreshing {
refreshControl.endRefreshing()
}
}
// Prints records to the console as expected…
print("projects", CoreStore.fetchAll(From(Project)))
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Deselect row on Back gesture. See: http://stackoverflow.com/a/23895557/171144
if let indexPath = tableView.indexPathForSelectedRow {
tableView.deselectRowAtIndexPath(indexPath, animated: animated)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let identifier = Segue(rawValue: segue.identifier!) else { return }
switch identifier {
case .ShowProject:
guard let indexPath = tableView.indexPathForSelectedRow else { return }
let destination = segue.destinationViewController as! ProjectController
let project = projects[indexPath]
destination.project = project
}
}
// MARK: UITableViewController
func refresh(sender: AnyObject) {
if let rootController = self.parentViewController as? RootController {
rootController.sync()
}
}
// MARK: UITableViewDataSource
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return projects.numberOfSections()
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return projects.numberOfObjectsInSection(section)
}
// Section headers
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let sectionInfo = self.projects.sectionInfoAtIndex(section)
return sectionInfo.name
}
// Project cells
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ProjectCell", forIndexPath: indexPath) as UITableViewCell
let project = projects[indexPath]
cell.textLabel!.text = project.name
return cell
}
override func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
// Don't display section index letters down right-hand side of table
return nil
}
// MARK: ListObserver
func listMonitorWillChange(monitor: ListMonitor<Project>) {
self.tableView.beginUpdates()
}
func listMonitorDidChange(monitor: ListMonitor<Project>) {
self.tableView.endUpdates()
}
func listMonitorWillRefetch(monitor: ListMonitor<Project>) {
self.setTableEnabled(false)
}
func listMonitorDidRefetch(monitor: ListMonitor<Project>) {
self.tableView.reloadData()
self.setTableEnabled(true)
}
// MARK: ListObjectObserver
func listMonitor(monitor: ListMonitor<Project>, didInsertObject object: Project, toIndexPath indexPath: NSIndexPath) {
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
func listMonitor(monitor: ListMonitor<Project>, didDeleteObject object: Project, fromIndexPath indexPath: NSIndexPath) {
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
func listMonitor(monitor: ListMonitor<Project>, didUpdateObject object: Project, atIndexPath indexPath: NSIndexPath) {
if let cell = self.tableView.cellForRowAtIndexPath(indexPath) {
let project = projects[indexPath]
cell.textLabel!.text = project.name
}
}
func listMonitor(monitor: ListMonitor<Project>, didMoveObject object: Project, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
self.tableView.deleteRowsAtIndexPaths([fromIndexPath], withRowAnimation: .Automatic)
self.tableView.insertRowsAtIndexPaths([toIndexPath], withRowAnimation: .Automatic)
}
// MARK: ListSectionObserver
func listMonitor(monitor: ListMonitor<Project>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic)
}
func listMonitor(monitor: ListMonitor<Project>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic)
}
}
```
Incidentally, the ListObserver demos in the CoreStoreDemo app initially appear empty too, which was unexpected (are they meant to start empty?) and led me to wonder if CoreStore might be at fault:
<img width="432" alt="screen shot 2016-02-08 at 17 33 00" src="https://cloud.githubusercontent.com/assets/5934/12893994/3e942fac-ce8c-11e5-8167-a9ad789ac58f.png">
Thanks.
adam
added the pending label 2025-12-29 15:22:47 +01:00
I can't find anything wrong in your code, architectually sound actually, but you can try check the following:
Is your tableView an IBOutlet? Tableviews automatically reload on layout, but you may want to try calling tableView.reloadData() at the end of viewDidLoad or viewWillAppear
put breakpoints on numberOfSections and numberOfRowsInSections and check the timing if called after viewDidLoad, and what the value being returned are
should be unrelated, but you can ommit the Where(true) clause
The demo app starts with a clean database, but after you add some items and try to restart the app, the previous entries should be visible.
@JohnEstropia commented on GitHub (Feb 9, 2016):
I can't find anything wrong in your code, architectually sound actually, but you can try check the following:
- Is your tableView an IBOutlet? Tableviews automatically reload on layout, but you may want to try calling `tableView.reloadData()` at the end of viewDidLoad or viewWillAppear
- put breakpoints on numberOfSections and numberOfRowsInSections and check the timing if called after viewDidLoad, and what the value being returned are
- should be unrelated, but you can ommit the `Where(true)` clause
The demo app starts with a clean database, but after you add some items and try to restart the app, the previous entries should be visible.
@jamesbebbington commented on GitHub (Feb 9, 2016):
I can't find anything wrong in your code, architectually sound actually,
Thanks, it's my first Cocoa app so that's good to hear.
but you can try check the following:
Is your tableView an IBOutlet? Tableviews automatically reload on layout, but you may want to try calling tableView.reloadData() at the end of viewDidLoad or viewWillAppear
It's not an IBOutlet but adding it as one and calling reloadData() doesn't help.
put breakpoints on numberOfSections and numberOfRowsInSections and check the timing if called after viewDidLoad, and what the value being returned are
Ah yes, I did try this, and should have mentioned it in my previous post. Adding breakpoints to the UITableViewDataSource methods reveals that numberOfSectionsInTableView is called six times, and each time projects.numberOfSections() returns 0, where I would expect it to return 2 for my dataset (as it does after an initial data fetch):
This led me to suspect the sectioned list monitor might be playing up, but converting it to a plain list monitor makes no difference:
should be unrelated, but you can ommit the Where(true) clause
Ah thanks, I wasn't sure about that.
The demo app starts with a clean database, but after you add some items and try to restart the app, the previous entries should be visible.
OK, thanks for the clarification, it wasn't obvious to me from the source what the expected behaviour was.
Ah ha! I definitely I tried adding this in various places before and it had no effect, but a projects.refetch() at the end of viewDidLoad() seems to do the trick.
Can we conclude that there is something not quite right with ListMonitor, as I presume that this shouldn't be necessary?
@jamesbebbington commented on GitHub (Feb 9, 2016):
> I can't find anything wrong in your code, architectually sound actually,
Thanks, it's my first Cocoa app so that's good to hear.
> but you can try check the following:
> - Is your tableView an IBOutlet? Tableviews automatically reload on layout, but you may want to try calling tableView.reloadData() at the end of viewDidLoad or viewWillAppear
It's not an `IBOutlet` but adding it as one and calling `reloadData()` doesn't help.
> - put breakpoints on numberOfSections and numberOfRowsInSections and check the timing if called after viewDidLoad, and what the value being returned are
Ah yes, I did try this, and should have mentioned it in my previous post. Adding breakpoints to the `UITableViewDataSource` methods reveals that `numberOfSectionsInTableView` is called six times, and each time `projects.numberOfSections()` returns `0`, where I would expect it to return `2` for my dataset (as it does after an initial data fetch):
<img width="432" alt="screen shot 2016-02-09 at 10 24 18" src="https://cloud.githubusercontent.com/assets/5934/12913544/510fc32c-cf17-11e5-8415-b4cc6a98e6cd.png">
This led me to suspect the sectioned list monitor might be playing up, but converting it to a plain list monitor makes no difference:
``` swift
let projects: ListMonitor<Project> = {
return CoreStore.monitorList(
From(Project),
OrderBy(.Ascending("state"), .Ascending("name"))
)
}()
```
> - should be unrelated, but you can ommit the Where(true) clause
Ah thanks, I wasn't sure about that.
> The demo app starts with a clean database, but after you add some items and try to restart the app, the previous entries should be visible.
OK, thanks for the clarification, it wasn't obvious to me from the source what the expected behaviour was.
---
Ah ha! I definitely I tried adding this in various places before and it had no effect, but a `projects.refetch()` at the end of `viewDidLoad()` seems to do the trick.
Can we conclude that there is something not quite right with `ListMonitor`, as I presume that this shouldn't be necessary?
Thanks for sharing your debugging info! Getting it fixed by calling refetch() (it definitely shouldn't be needed) makes me think that your ListMonitor is getting initialized before the NSPersistentStore gets added completely to the stack.
Can you share how your addSQLiteStore...() is setup and when it gets called?
Is your view controller the storyboard's initial view controller? (or a child of the initial controller?)
My guess is either:
You are using the aynchonous form of addSQLiteStore(...) and the view controller is created before the completion is called. Or,
You are using the synchonous addSQLiteStoreAndWait(...) one but the view controller is created right after it on the same run loop. (if so, this could be be a bug in CoreStore or an undocumented behavior of NSPersistentStoreCoordinator)
@JohnEstropia commented on GitHub (Feb 9, 2016):
Thanks for sharing your debugging info! Getting it fixed by calling `refetch()` (it definitely shouldn't be needed) makes me think that your ListMonitor is getting initialized before the NSPersistentStore gets added completely to the stack.
- Can you share how your `addSQLiteStore...()` is setup and when it gets called?
- Is your view controller the storyboard's initial view controller? (or a child of the initial controller?)
My guess is either:
1. You are using the aynchonous form of `addSQLiteStore(...)` and the view controller is created before the completion is called. Or,
2. You are using the synchonous `addSQLiteStoreAndWait(...)` one but the view controller is created right after it on the same run loop. (if so, this could be be a bug in CoreStore or an undocumented behavior of NSPersistentStoreCoordinator)
@jamesbebbington commented on GitHub (Feb 9, 2016):
Thanks for sharing your debugging info!
You're welcome, hopefully it'll help others who might have similar issues too.
Getting it fixed by calling refetch() (it definitely shouldn't be needed) makes me think that your ListMonitor is getting initialized before the NSPersistentStore gets added completely to the stack.
Ah, yeah that sounds like a possibility.
Can you share how your addSQLiteStore...() is setup and when it gets called?
I'm calling CoreStore.addSQLiteStoreAndWait() in applicationDidFinishLaunching(application: UIApplication).
Is your view controller the storyboard's initial view controller? (or a child of the initial controller?)
Yes, indirectly. The ProjectsController is a child of my RootController which is the initial controller.
My guess is either:
You are using the aynchonous form of addSQLiteStore(...) and the view controller is created before the completion is called. Or,
You are using the synchonous addSQLiteStoreAndWait(...) one but the view controller is created right after it on the same run loop. (if so, this could be be a bug in CoreStore or an undocumented behavior of NSPersistentStoreCoordinator)
Yeah, it looks like the latter is the problem. If I remove the refetch() workaround and change the ListMonitor property from a constant to a lazy var the problem goes away:
@jamesbebbington commented on GitHub (Feb 9, 2016):
> Thanks for sharing your debugging info!
You're welcome, hopefully it'll help others who might have similar issues too.
> Getting it fixed by calling refetch() (it definitely shouldn't be needed) makes me think that your ListMonitor is getting initialized before the NSPersistentStore gets added completely to the stack.
Ah, yeah that sounds like a possibility.
> Can you share how your addSQLiteStore...() is setup and when it gets called?
I'm calling `CoreStore.addSQLiteStoreAndWait()` in `applicationDidFinishLaunching(application: UIApplication)`.
> Is your view controller the storyboard's initial view controller? (or a child of the initial controller?)
Yes, indirectly. The `ProjectsController` is a child of my `RootController` which is the initial controller.
> My guess is either:
> 1. You are using the aynchonous form of addSQLiteStore(...) and the view controller is created before the completion is called. Or,
> 2. You are using the synchonous addSQLiteStoreAndWait(...) one but the view controller is created right after it on the same run loop. (if so, this could be be a bug in CoreStore or an undocumented behavior of NSPersistentStoreCoordinator)
Yeah, it looks like the latter is the problem. If I remove the `refetch()` workaround and change the `ListMonitor` property from a constant to a lazy var the problem goes away:
``` swift
lazy var projects: ListMonitor<Project> = {
return CoreStore.monitorSectionedList(
From(Project),
SectionBy("state") { (sectionName) -> String? in
sectionName?.capitalizedString
},
OrderBy(.Ascending("state"), .Ascending("name"))
)
}()
```
Okay, I'll try to see if there's a way to handle this from within CoreStore. I'm not sure how to proceed so far though, as I already use NSPersistentStoreCoordinator.performBlockAndWait { ... }.
Anyway I'm glad using lazy fixed it for you. You can also skip the closure altogether and just declare it this way:
@JohnEstropia commented on GitHub (Feb 9, 2016):
Okay, I'll try to see if there's a way to handle this from within CoreStore. I'm not sure how to proceed so far though, as I already use `NSPersistentStoreCoordinator.performBlockAndWait { ... }`.
Anyway I'm glad using `lazy` fixed it for you. You can also skip the closure altogether and just declare it this way:
``` swift
lazy var projects: ListMonitor<Project> = CoreStore.monitorSectionedList(
From(Project),
SectionBy("state") { (sectionName) -> String? in
sectionName?.capitalizedString
},
OrderBy(.Ascending("state"), .Ascending("name"))
)
```
@jamesbebbington commented on GitHub (Feb 9, 2016):
Okay, I'll try to see if there's a way to handle this from within CoreStore. I'm not sure how to proceed so far though, as I already use NSPersistentStoreCoordinator.performBlockAndWait { ... }.
Sounds like a tricky one. Maybe there's some inspiration to be found in the source of Signals and its listenPast functionality.
You can also skip the closure altogether…
Ah yes, cheers and thanks for all your help on this one!
@jamesbebbington commented on GitHub (Feb 9, 2016):
> Okay, I'll try to see if there's a way to handle this from within CoreStore. I'm not sure how to proceed so far though, as I already use `NSPersistentStoreCoordinator.performBlockAndWait { ... }`.
Sounds like a tricky one. Maybe there's some inspiration to be found in the source of [`Signals`](https://github.com/artman/Signals) and its `listenPast` functionality.
> You can also skip the closure altogether…
Ah yes, cheers and thanks for all your help on this one!
Sounds like a tricky one. Maybe there's some inspiration to be found in the source of Signals and its listenPast functionality.
Yup, if I find a notification to listen for it might be easy to implement on addSQLiteStore(..., completion:) (async version). My concern is if it is possible at all for the synchronous addSQLiteStoreAndWait(...) method.
I'll try to see if there's a workaround. Thanks for the detailed investigation!
@JohnEstropia commented on GitHub (Feb 10, 2016):
@fractious
> Sounds like a tricky one. Maybe there's some inspiration to be found in the source of Signals and its listenPast functionality.
Yup, if I find a notification to listen for it might be easy to implement on `addSQLiteStore(..., completion:)` (async version). My concern is if it is possible at all for the synchronous `addSQLiteStoreAndWait(...)` method.
I'll try to see if there's a workaround. Thanks for the detailed investigation!
@fractious Can I ask one more thing? What iOS version are you having this problem?
I can't seem to reproduce this with the demo app on iOS 9 (even if I set the Colors Demo as the initial view controller)
@JohnEstropia commented on GitHub (Feb 11, 2016):
@fractious Can I ask one more thing? What iOS version are you having this problem?
I can't seem to reproduce this with the demo app on iOS 9 (even if I set the Colors Demo as the initial view controller)
The only way I have reproduced this is to call addSQLiteStoreAndWait() right after monitorSectionedList()... Can you check again with breakpoints which gets called first in your previous setup? (without the lazy fix)
@JohnEstropia commented on GitHub (Feb 11, 2016):
The only way I have reproduced this is to call `addSQLiteStoreAndWait()` right after `monitorSectionedList()`... Can you check again with breakpoints which gets called first in your previous setup? (without the `lazy` fix)
@jamesbebbington commented on GitHub (Feb 11, 2016):
What iOS version are you having this problem?
I've been using iOS 9.2 in the iPhone 5s simulator.
The only way I have reproduced this is to call addSQLiteStoreAndWait() right after monitorSectionedList()... Can you check again with breakpoints which gets called first in your previous setup? (without the lazy fix)
Yeah that what appears to be happening, my ProjectsController is instantiated before applicationDidFinishLaunching() is called.
An alternative fix to lazily instantiating the ListMonitor is to move the addSQLiteStoreAndWait() to a property declaration, so that it fires first:
Obviously the above example will cause the app to crash if the method throws, but it could always be changed to a computed property that catches.
Is the above something you'd recommend over the lazy alternative?
@jamesbebbington commented on GitHub (Feb 11, 2016):
> What iOS version are you having this problem?
I've been using iOS 9.2 in the iPhone 5s simulator.
> The only way I have reproduced this is to call `addSQLiteStoreAndWait()` right after `monitorSectionedList()`... Can you check again with breakpoints which gets called first in your previous setup? (without the `lazy` fix)
Yeah that what appears to be happening, my `ProjectsController` is instantiated before `applicationDidFinishLaunching()` is called.
An alternative fix to lazily instantiating the `ListMonitor` is to move the `addSQLiteStoreAndWait()` to a property declaration, so that it fires first:
``` swift
class AppDelegate: UIResponder {
let persistentStore = try! CoreStore.addSQLiteStoreAndWait()
…
}
```
Obviously the above example will cause the app to crash if the method `throws`, but it could always be changed to a computed property that `catch`es.
Is the above something you'd recommend over the `lazy` alternative?
@fractious I see, so it's not a problem with Core Data after all. I'll add an assert() on ListMonitor to prevent getting created without any persistent stores.
As for your app, you can adopt the style that the demo app uses:
I'll close this issue as it seems to be fixable on the client side. I already added the assert on my local repository so it should be in the develop branch soon. Thanks again for the prompt responses! If there's anything else feel free to message anytime :)
@JohnEstropia commented on GitHub (Feb 11, 2016):
@fractious I see, so it's not a problem with Core Data after all. I'll add an `assert()` on ListMonitor to prevent getting created without any persistent stores.
As for your app, you can adopt the style that the demo app uses:
``` swift
let projects: ListMonitor<Project> = {
try! CoreStore.addSQLiteStoreAndWait(
fileName: "YourFileName.sqlite",
configuration: "YourConfiguration",
resetStoreOnModelMismatch: true
)
return CoreStore.monitorSectionedList(
From(Project),
SectionBy("state") { (sectionName) -> String? in
sectionName?.capitalizedString
},
OrderBy(.Ascending("state"), .Ascending("name"))
)
}()
```
I'll close this issue as it seems to be fixable on the client side. I already added the `assert` on my local repository so it should be in the develop branch soon. Thanks again for the prompt responses! If there's anything else feel free to message anytime :)
@jamesbebbington commented on GitHub (Feb 11, 2016):
Cheers @JohnEstropia and thanks for helping get to the bottom of this one, much appreciated.
I think I'll keep my CoreStore.addSQLiteStoreAndWait() in the AppDelegate as I've got a number of ListSectionObserver controllers so would like to keep them DRY. I'll run with non-lazy fix for now and see how that goes.
@jamesbebbington commented on GitHub (Feb 11, 2016):
Cheers @JohnEstropia and thanks for helping get to the bottom of this one, much appreciated.
I think I'll keep my `CoreStore.addSQLiteStoreAndWait()` in the `AppDelegate` as I've got a number of `ListSectionObserver` controllers so would like to keep them DRY. I'll run with non-`lazy` fix for now and see how that goes.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Originally created by @jamesbebbington on GitHub (Feb 8, 2016).
Having implemented a sectioned
ListMonitoras the datasource for my controller, I'm finding that it only returns records when the underlying store content changes.I boot my app, insert data into the store and the table view is updated accordingly. But when I restart the app, the table view is empty. However, a
CoreStore.fetchAllreveals that there is data in the store.Am I doing anything obviously wrong?
Incidentally, the ListObserver demos in the CoreStoreDemo app initially appear empty too, which was unexpected (are they meant to start empty?) and led me to wonder if CoreStore might be at fault:
Thanks.
@JohnEstropia commented on GitHub (Feb 9, 2016):
I can't find anything wrong in your code, architectually sound actually, but you can try check the following:
tableView.reloadData()at the end of viewDidLoad or viewWillAppearWhere(true)clauseThe demo app starts with a clean database, but after you add some items and try to restart the app, the previous entries should be visible.
@jamesbebbington commented on GitHub (Feb 9, 2016):
Thanks, it's my first Cocoa app so that's good to hear.
It's not an
IBOutletbut adding it as one and callingreloadData()doesn't help.Ah yes, I did try this, and should have mentioned it in my previous post. Adding breakpoints to the
UITableViewDataSourcemethods reveals thatnumberOfSectionsInTableViewis called six times, and each timeprojects.numberOfSections()returns0, where I would expect it to return2for my dataset (as it does after an initial data fetch):This led me to suspect the sectioned list monitor might be playing up, but converting it to a plain list monitor makes no difference:
Ah thanks, I wasn't sure about that.
OK, thanks for the clarification, it wasn't obvious to me from the source what the expected behaviour was.
Ah ha! I definitely I tried adding this in various places before and it had no effect, but a
projects.refetch()at the end ofviewDidLoad()seems to do the trick.Can we conclude that there is something not quite right with
ListMonitor, as I presume that this shouldn't be necessary?@JohnEstropia commented on GitHub (Feb 9, 2016):
Thanks for sharing your debugging info! Getting it fixed by calling
refetch()(it definitely shouldn't be needed) makes me think that your ListMonitor is getting initialized before the NSPersistentStore gets added completely to the stack.addSQLiteStore...()is setup and when it gets called?My guess is either:
addSQLiteStore(...)and the view controller is created before the completion is called. Or,addSQLiteStoreAndWait(...)one but the view controller is created right after it on the same run loop. (if so, this could be be a bug in CoreStore or an undocumented behavior of NSPersistentStoreCoordinator)@jamesbebbington commented on GitHub (Feb 9, 2016):
You're welcome, hopefully it'll help others who might have similar issues too.
Ah, yeah that sounds like a possibility.
I'm calling
CoreStore.addSQLiteStoreAndWait()inapplicationDidFinishLaunching(application: UIApplication).Yes, indirectly. The
ProjectsControlleris a child of myRootControllerwhich is the initial controller.Yeah, it looks like the latter is the problem. If I remove the
refetch()workaround and change theListMonitorproperty from a constant to a lazy var the problem goes away:@JohnEstropia commented on GitHub (Feb 9, 2016):
Okay, I'll try to see if there's a way to handle this from within CoreStore. I'm not sure how to proceed so far though, as I already use
NSPersistentStoreCoordinator.performBlockAndWait { ... }.Anyway I'm glad using
lazyfixed it for you. You can also skip the closure altogether and just declare it this way:@jamesbebbington commented on GitHub (Feb 9, 2016):
Sounds like a tricky one. Maybe there's some inspiration to be found in the source of
Signalsand itslistenPastfunctionality.Ah yes, cheers and thanks for all your help on this one!
@JohnEstropia commented on GitHub (Feb 10, 2016):
@fractious
Yup, if I find a notification to listen for it might be easy to implement on
addSQLiteStore(..., completion:)(async version). My concern is if it is possible at all for the synchronousaddSQLiteStoreAndWait(...)method.I'll try to see if there's a workaround. Thanks for the detailed investigation!
@JohnEstropia commented on GitHub (Feb 11, 2016):
@fractious Can I ask one more thing? What iOS version are you having this problem?
I can't seem to reproduce this with the demo app on iOS 9 (even if I set the Colors Demo as the initial view controller)
@JohnEstropia commented on GitHub (Feb 11, 2016):
The only way I have reproduced this is to call
addSQLiteStoreAndWait()right aftermonitorSectionedList()... Can you check again with breakpoints which gets called first in your previous setup? (without thelazyfix)@jamesbebbington commented on GitHub (Feb 11, 2016):
I've been using iOS 9.2 in the iPhone 5s simulator.
Yeah that what appears to be happening, my
ProjectsControlleris instantiated beforeapplicationDidFinishLaunching()is called.An alternative fix to lazily instantiating the
ListMonitoris to move theaddSQLiteStoreAndWait()to a property declaration, so that it fires first:Obviously the above example will cause the app to crash if the method
throws, but it could always be changed to a computed property thatcatches.Is the above something you'd recommend over the
lazyalternative?@JohnEstropia commented on GitHub (Feb 11, 2016):
@fractious I see, so it's not a problem with Core Data after all. I'll add an
assert()on ListMonitor to prevent getting created without any persistent stores.As for your app, you can adopt the style that the demo app uses:
I'll close this issue as it seems to be fixable on the client side. I already added the
asserton my local repository so it should be in the develop branch soon. Thanks again for the prompt responses! If there's anything else feel free to message anytime :)@jamesbebbington commented on GitHub (Feb 11, 2016):
Cheers @JohnEstropia and thanks for helping get to the bottom of this one, much appreciated.
I think I'll keep my
CoreStore.addSQLiteStoreAndWait()in theAppDelegateas I've got a number ofListSectionObservercontrollers so would like to keep them DRY. I'll run with non-lazyfix for now and see how that goes.