CoreData, threading, main context and private context - works 99% of the time but - swift

In my app I use two contexts: app delegate main context and a private context.
The private context is set as follows:
var privateContext: NSManagedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
privateContext.persistentStoreCoordinator = context.persistentStoreCoordinator
I also set an observer on the private context to trigger a save via main context:
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(MyView.managedObjectContextDidSave(_:)), name: NSManagedObjectContextDidSaveNotification, object: self.privateContext)
I trigger a data download with a callback to the current ViewController. During the download, I process all objects in:
privateContext.performBlock {
// process objects
....
// now save
if self.privateContext.hasChanges {
privateDataManager.save()
}
}
The save in the private context, triggers the observer and this code gets invoked:
dispatch_async(AppUtils.GlobalMainQueue, {
self.context.mergeChangesFromContextDidSaveNotification(notification)
})
The problem is that every now and then, not all changes get persisted. Cannot say when or why - when I debug it it always works...
How do I know I have a problem? Well, I compare object count before and after the download. To add more 'colour':
Every download of data adds some new records. The app then selects which records are out-of-date and marks them for deletion (sets a flag on a record). It then saves them (new and 'to be deleted') in the private context. The 'save' triggers a 'merge into primary context' notification.
In theory, this notification triggers the 'merge' in a synchronous manner.
After the merge, assuming it does happen in-order, there is data reload - this reload only loads records that do not have the 'deleted' flag set.
The problem I am having is that the 'merge' does not seem to always happen before I reload (that's the only way I can explain this).
My question, assuming my analysis is correct, is how to force the merge to happen before the reload? Does the notification not happen in a synchronous manner?
I guess I could always save the private context and instead of triggering the notification, simply create a notification object using private context and force trigger the merge. But I would like to understand why the above code does not work as expected.
The main source on which I based my code can be found here.

Related

RxJava2 action not executed if user leaves screen

I do a remove action through RxJava2 that causes a refresh on my local cache like this:
override fun removeExperience(experienceId: String, placeId: String): Completable {
return from(placesApi.deleteExperience(experienceId, placeId))
.andThen(from(refreshPlace(placeId))
.flatMapCompletable { Completable.complete() }
)
}
so whenever the remove action is done (Completable is complete), a refresh is triggered. The problem is, sometimes this remove action takes long enough for users to just leave the screen, and then the andThen action is never executed cause there is no subscribers anymore, and thus the information on the screen is not up to date anymore.
Is there a way to enforce this action to take place?
Does this logic continue working when user open the same screen again? If so, then you only need to finish subscription from(placesApi.deleteExperience(experienceId, placeId)) on lifecycle events. The easiest way is to add the whole subscription removeExperience() to Disposable or CompositeDisposable and then trigger its .dispose() or .clear() on view stop or destroy events.
.dispose() - doesn't allow to use the same subscription stored.
.clear() - allows re-subscription without creating the new
subscription instance

Background Process as NSOperation or Thread to monitor and update File

I want to check if a pdf file is changed or not, and if is changed i want to update the corresponding view. I don't know if it's more suitable to use a background process as a Thread or as an NSOperation to do this task. The Apple Documentation says: "Examples of tasks that lend themselves well to NSOperation include network requests, image resizing, text processing, or any other repeatable, structured, long-running task that produces associated state or data.But simply wrapping computation into an object doesn’t do much without a little oversight".
Also, if I understood correctly from the documentation, a Thread once started can't be stopped during his execution while an NSOperation could be paused or stopped and also they could rely on dependency to wait the completion of another task.
The workflow of this task should be more or less this diagram:
Task workflow
I managed to get the handler working after the notification of type .write has been sent. If i monitor for example a *.txt file everything works as expected and i receive only one notification. But i am monitoring a pdf file which is generated from terminal by pdflatex and thus i receive with '.write' nearly 15 notification. If i change to '.attrib' i get 3 notification. I need the handler to be called only once, not 15 or 3 times. Do you have any idea how can i do it or is not possible with a Dispatch Source? Maybe there is a way to execute a dispatchWorkItem only once?
I have tried to implement it like this(This is inside a FileMonitor class):
func startMonitoring()
{
....
let fileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: fileStringURL)
let fileDescriptor = open(fileSystemRepresentation, O_EVTONLY)
let newfileMonitorSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fileDescriptor,
eventMask: .attrib,
queue: queue)
newfileMonitorSource.setEventHandler(handler:
{
self.queue.async
{
print(" \n received first write event, removing handler..." )
self.newfileMonitorSource.setEventHandler(handler: nil)
self.test()
}
})
self.fileMonitorSource = newfileMonitorSource
fileMonitorSource!.resume()
}
func test()
{
fileMonitorSource?.cancel()
print(" restart monitoring ")
startMonitoring()
}
I have tried to reassign the handler in test(), but it's not working(if a regenerate the pdf file, what is inside the new handler it's not executed) and to me, doing in this way, it seems a bit boilerplate code. I have also tried the following things:
suspend the DispatchSource in the setEventHandler of startMonitoring() (passing nil), but then when i am resuming it, i get the remaining .write events.
cancel the DispatchSource object and recall the startMonitoring() as you can see in the code above, but in this way i create and destroy the DispatchSource object everytime i receive an event, which i don't like because the cancel() function shoul be called in my case only when the user decide to disable this feauture i am implementing.
I will try to write better how the workflow of the app should be so you can have an more clear idea of what i am doing:
When the app starts, a functions sets the default value of some checkboxes of the window preference. The user can modify this checkboxes. So when the user open a pdf file, the idea is to launch in a background thread the following task:
I create a new queue call it A and launch asynch an infinite while where i check the value of the UserDefault checkboxe (that i use to reload and update the pdf file) and two things could happen
if the user set the value to off and the pdf document has been loaded there could be two situations:
if there is no current monitoring of the file (when the app starts): continue to check the checkboxe value
if there is currently a monitoring of the file: stop it
if the user set value to on and the pdf document has been loaded in this background thread (the same queue A) i will create a class Monitor (that could be a subclass of NSThread or a class that uses DispatchSourceFileSystemObject like above), then i will call startMonitoring() that will check the date or .write events and when there is a change it will call the handler. Basically this handler should recall the main thread (the main queue) and check if the file can be loaded or is corrupted and if so update the view.
Note: The infinite while loop(that should be running in the background), that check the UserDefault related to the feature i am implementing it's launched when the user open the pdf file.
Because of the problem above (multiple handlers calls), i should use the cancel() function when the user set checkboxe to off, and not create/destroy the DispatchSource object everytime i receive a .write event.

When are Realm notifications delivered to main thread after writes on a background thread?

I'm seeing crashes that either shouldn't be possible, or are very much possible and the documentation just isn't clear enough as to why.
UPDATE:
Although I disagree with the comment below asking me to separate this into multiple SO questions, if someone could focus on this one I think it would help greatly:
When are notifications delivered to the main thread? Is it possible that the results on the main thread are different than they were in a previous runloop without being notified yet of the difference?
If the answer to this question is yes the results could be different than a previous runloop without notifying then I would argue it is CRUCIAL to get this into the documentation somewhere.
Background Writes
First I think it's important to go over what I am already doing for writes. All of my writes are performed through a method that essentially looks like this (error handling aside):
func write(block: #escaping (Realm) -> ()) {
somePrivateBackgroundSerialQueue.async {
autoreleasepool {
let realm = try! Realm()
realm.refresh()
try? realm.write { block(realm) }
}
}
}
Nothing crazy here, pretty well documented on your end.
Notifications and Table Views
The main question I have here is when are notifications delivered to the main thread after being written from a background thread? I have a complex table view (multiple sections, ads every 5th row) backed by realm results. My main data source looks like:
enum StoryRow {
case story(Story) // Story is a RealmSwift.Object subclass
case ad(Int)
}
class StorySection {
let stories: Results<Story>
var numberOfRows: Int {
let count = stories.count
return count + numberOfAds(before: count)
}
func row(at index: Int) -> StoryRow {
if isAdRow(at: index) {
return .ad(index)
} else {
let storyIndex = index - numberOfAds(before: index)
return .story(stories[storyIndex])
}
}
}
var sections: [StorySection]
... sections[indexPath.section].row(at: indexPath.row) ...
Before building my sections array I fetch the realm results and filter them based on the type of stories for the particular screen, sort them so they are in the proper order for their sections, then I build up the sections by passing in results.filter(...date query...) to the section constructor. Finally, I results.observe(...) the main results object (not any of the results passed into the section) and reload the table view when the notification handler is called. I don't bother observing the results in the sections because if any of those results changed then the parent had to change as well and it should trigger a change notification.
The ad slots have callbacks when an ad is filled or not filled and when that happens instead of calling tableView.reloadData() I am doing something like:
guard tableView.indexPathsForVisibleRows?.contains(indexPath) == true else { return }
tableView.beginUpdates()
tableView.reloadRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
The problem is, I very rarely see a crash either around an index being out of bound when accessing the realm results or an invalid table view update.
QUESTIONS
Is it possible the realm changed on the main thread before any notifications were delivered?
Should table view updates other than reloadData() simply not be used anywhere outside of a realm notification block?
Anything else crucial I am missing?
There's nothing in your code snippets or description of what you're doing that jumps out at me as obviously wrong. Having a separate callback mechanism that updates specific slots independent of Realm change notifications has a lot of potential for timing related bugs, but since you're explicitly checking if the indexPath is visible before reloading the row, I would expect that to at worst manifest as reloading the wrong row and not a crash.
The intended behavior is that refreshing the Realm and delivering notifications is an atomicish operation: anything that causes the read version to advance will deliver all notifications before returning. In simple cases, this means that you'll never see the new data without the associated notification firing first. However, there's some caveats to this:
Nested notification delivery doesn't work correctly, so beginning a write transaction from within a notification block can result in a notification being skipped (merely calling refresh() can't cause this, as it's just a no-op within a notification). If you're performing all writes on background threads you shouldn't be hitting this.
If you have multiple notification blocks, then obviously anything which gets invoked from the first one will run before the second notification block gets a chance to do things, and a call to tableView.reloadData() may result in quite a lot of things happening within the notification block. If this is the source of problems, you would hopefully see exceptions being thrown with a stack trace coming from within a notification block.

Simple waiting for value from async thread

I've got 2 basic methods - viewDidLoad and viewDidAppear. According to my App philosophy, when view controller loads, it fetches data from base and starts to sort it with some predicates. Fetching process is long, so I dispatched it to global queue. When my view appears, it obviously do not get the value from array(which compiles in load method) and crashes. So I need viewDidAppear to wait till at least one object will be appended to array.
Kind of semaphores or temp values?
Thanks in advance!
P.S. Each item in array represent struct with data which composes UI. User interact with this UI, so it has to be loaded once with the first item from array. To switch to next item, user just clicks "next" and UI changes according to next item from array. That's why I want the data to fetch in background and allow user to work immediately. (It's impossible to jump on 5th, 10th or 1001st element immediately, there will be enough time to fetch data before user gets on these page numbers)
P.P.S Still no right decision :(
You should using a nested dispatch block, like so:
func fetch(completion block:(() -> Void)?) {
// Run fetch on background thread, to prevent the main thread (and hence your UI) from being 'blocked'.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
//
// Fetch data...
//
dispatch_async(dispatch_get_main_queue(), {
block?()
})
})
}
fetch(completion: {
// Update your UI
})

Nested undo group with CoreData

I want to add a undo manager to a coredata backed iphone application. When the user tries to add a new object (by tapping on + button) I load a new modal viewcontroller and start a new undo group in viewDidLoad.
When the user presses Cancel button, I want to rollback the changes in cancelAction callback.
Queries:
Is it possible to start a nested Undo group and persist it through event loop and collate all the changes done in one single undo group? Right now, when I call beginUndoGrouping in cancelAction, I get a missing beginUndoGrouping exception.
What is the significance of groupsByEvent API for NSUndoManager? Do I require to set it to NO to persist an undo group through event loop?
What needs to be done in saveAction callback to make changes permanent (apart from calling save on mangedObjectContext)? How do I tell Undo Manager to stop tracking the changes?
Error Message:
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '_endUndoGroupRemovingIfEmpty:: NSUndoManager 0x75415f0 is in invalid state, endUndoGrouping called with no matching begin
Sample Code:
// RootViewController.m
- (void) addAction {
// Load Modal View Controller to add new object
}
// AddViewController.m
- (void) viewDidLoad {
// Start nested undo group
[self.managedObjectContext processPendingChanges];
[self.managedObjectContext.undoManager beginUndoGrouping];
[self createModel];
}
- (void) cancelAction {
// Revert all changes
[self.managedObjectContext processPendingChanges];
[self.managedObjectContext.undoManager endUndoGrouping];
[self.managedObjectContext.undoManager undoNestedGroup];
...
}
- (void) saveAction {
// Save changes
}
Beginning with your specific questions - Yes, you can manually define the bounds of an undo operation using beginUndoGrouping and endUndoGrouping.
In this case, the undo operation should work whether or not groupsByEvent is set. This is because all the undo groups that are generated by the event loop are nested under your main open undo grouping started with beginUndoGrouping, and as long as you call undoNestedGroup directly after you call endUndoGrouping, it should work. If you are not using the event loop undo groupings, don't worry about it and set it to NO.
To make your changes permanent, close the undo group with endUndoGrouping and call save on your context. The processPendingChanges calls are not needed, and may cause issues in nested groups. If you want clear the undo operations, call removeAllActions on your undomanager after endUndoGrouping - this guarantees the changes will never be un-did.
Use breakpoints/nslog to make sure your begin/end calls are one for one.
If you want your cancel action to be like an 'undo button', you'll have to do the following:
Move beginUndoGrouping to viewWillAppear
Call endUndoGrouping in viewWillDisappear
re-open undo grouping at the end of your cancel action
re-open undo grouping at the end of your save action
Otherwise, if you leave it as is, make sure you close the dialog in your save and cancel actions, to avoid possibility of endUndoGrouping being called multiple times.
If you have any questions, please comment and I'll update.
Good luck!