Core Data code and multithreading - multithreading

The following code is fetch data (fill data for the first time) part of my tableViewController. I am using an NSManagedDocument's managedObjectContext to fill (pre populate) my database. The source is an array that I clean up from my TXT file which rests directly in the Xcode's resources folder. After this creation, I have document cases like closed / open and normal.
The following code inputs and fetches my data onto the table correctly with a fetched results controller request. However, when the data is loading in the thread that is meant to free the UI from this one time data creation (26854 object names) into managedObject.name attribute heavy operation, the tableview and my UI is frozen (for 1-15 seconds that is I think while populating in document.managedObjectContext for the first time for my database).
After 10-15 seconds data is loaded and shows correctly. However, when I stop the simulator and restart the app in simulator, although I save the document as seen in below code, and I use the same fetch results controller setup (and request) the table view shows empty, it is movable in this case (The document state shows open and normal at this stage and file path is same, I checked... It seems like neither autosave nor explicit saveForOverwriting I use work... Or is it something else? I tried a lot of things and I'll go crazy soon. I think it has something to do with my multithreading.
self.managedObjectNames is the array property in the table view and I set it from the TXT file during my table view's loadView:
Is there anybody out there who can show the mistake here? Is it that I give self.managedObjectNames in the method of entity creation category.
Thanks!
- (void)fetchDataIntoDocument:(UIManagedDocument *)document {
dispatch_queue_t fetchQ = dispatch_queue_create("Data fetcher", NULL);
dispatch_async(fetchQ, ^{
[document.managedObjectContext performBlock:^{
for (int i = 0; i < 26854; i++) {
[managedObject managedObjectWithId:[NSNumber numberWithInt:i] andArray:self.managedObjectNames inManagedObjectContext:document.managedObjectContext];
}
// NSLog(#"Save baby!!?");
[document saveToURL:document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:nil];
}];
});
dispatch_release(fetchQ);
}

The reason why your UI is blocked for 10-15 seconds is because the document.managedObjectContext has been created with NSMainQueueConcurrencyType. That means that the performBlock: method will be executed on the main queue.
Creating the fetchQ in your code does not have any reason. It would have a reason if fetching of data would take some considerable amount of time but adding them would be fast (e.g. creating/modifying only few objects):
dispatch_async(fetchQ, ^{
// fetch data here (e.g. fetchAttribute may take few seconds)
NSString *attribute = fetchAttribute();
[document.managedObjectContext performBlock:^{
MyObject *o;
o = [NSEntityDescription insertNewObjectForEntityForName:#"MyObject"
inManagedObjectContext:document.managedObjectContext];
o.myAttribute = attribute;
}];
});
However I don't know answer to your main question.

Related

How to handle watchOS CoreData background save correctly?

My watchOS app uses core data for local storage. Saving the managed context is done in background:
var backgroundContext = persistentContainer.newBackgroundContext()
//…
backgroundContext.perform {
//…
let saveError = self.saveManagedContext(managedContext: self.backgroundContext)
completion(saveError)
}
//…
func saveManagedContext(managedContext: NSManagedObjectContext) -> Error? {
if !managedContext.hasChanges { return nil }
do {
try managedContext.save()
return nil
} catch let error as NSError {
return error
}
}
Very rarely, my context is not saved. One reason I can think of is the following:
After my data are changed, I initiate a background core data context save operation.
But before the background task starts, the watch extension is put by the user into background, and is then terminated by watchOS.
This probably also prevents the core data background save to execute.
My questions are:
- Is this scenario possible?
- If so, what would be the correct handling of a core data background context save?
PS: On the iOS side, I do the same, but here it is possible to request additional background processing time using
var bgTask: UIBackgroundTaskIdentifier = application.beginBackgroundTask(expirationHandler: {
//…
application.endBackgroundTask(bgTask)
}
By now, I think I can answer my question:
If the watch extension is put by the user into background, the extension delegate calls applicationDidEnterBackground(). The docs say:
The system typically suspends your app shortly after this method
returns; therefore, you should not call any asynchronous methods from
your applicationDidEnterBackground() implementation. Asynchronous
methods may not be able to complete before the app is suspended.
I think this also applies to background tasks that have been initiated before, so it is actually possible that a core data background save does not complete.
Thus, the core data save should be done on the main thread. My current solution is the following:
My background context is no longer set up using persistentContainer.newBackgroundContext(), since such a context is connected directly to the persistentContainer, and when this context is saved, changes are written to the persistent store, which may take relatively long. Instead, I now set up the background context by
var backgroundContext = NSManagedObjectContext.init(concurrencyType: .privateQueueConcurrencyType)
and set its parent property as
backgroundContext.parent = container.viewContext
where container is the persistent container. Now, when the background context is saved, it is not written to the persistent store, but to its parent, the view content that is handled by the main thread. Since this saving is only done in memory, it is pretty fast.
Additionally, in applicationDidEnterBackground() of the extension delegate, I save the view context. Since this is done on the main thread, The docs say:
The applicationDidEnterBackground() method is your last chance to
perform any cleanup before the app is terminated.
In normal circumstances, enough time should be provided by watchOS. If not, other docs say:
If needed, you can request additional background execution time by
calling the ProcessInfo class’s
performExpiringActivity(withReason:using:) method.
This is probably equivalent to setting up a background task in iOS as shown in my question.
Hope this helps somebody!

Nodejs Wait.For not working

I have a really annoying issue. Basically my code works in all other places apart from in this one instance where wait.for doesn't wait for the redis command to finish before moving on. The scenario is I trying to process an XML file and update records within a redis DB as the XML file is being read. The part that is breaking is where I'm trying to retrieve a list of customer card numbers within a region before updating the overall customer record and counts. But it is not waiting.
Here is the code:
function processCredit(creditObj)
{
.....
console.log('CREDIT CARD=1');
wait.launchFiber( updateCustomer, cardNumber );
}
function updateCustomer(cardNumber)
{
var foundCard=0;
console.log('CREDIT CARD=2');
var customerIDs = wait.forMethod(redisClient, 'zrange', 'cn:'+cardNumber+':'+stores[siteId]['region'], 0, -1);
console.log('CREDIT CARD=3 card number='+cardNumber);
....
}
"CREDIT CARD=3" statement always gets printed at the end of the log file, not directly after "CREDIT CARD=2"
I've used wait.for in many other places throughout my programme and it has always worked. I don't understand why it doesn't in this case.
Thanks

Magical Record Truncate not working after app terminate

hey all i've code like this
if ([cellValue isEqualToString:#"Logout"])
{
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];
[EUserBrief MR_truncateAllInContext:localContext];
[DownloadList MR_truncateAllInContext:localContext];
}
the code i want to truncate all data on entities [EUserbrief] and [DownloadList].. the code working fine, but after app terminate and i get in , EuserBrief data and downloadlist still there,,, any solution for this?
You need to call
[localContext MR_save];
Your changes will not be persisted to any data store without saving.

New project New Model NSPersistentDocument This NSPersistentStoreCoordinator has no persistent stores

I have been searching stackoverflow and Googling for hours. I made a simple project to mess around with Core Data and bindings. It added an entity to the model and it wouldn't work any more. I was getting "This NSPersistentStoreCoordinator has no persistent stores It cannot perform a save operation" whenever I tried to add data to a new document. I followed every piece of advice I could find with no luck.
Finally, I made a new project (NSPersistentDocument based) and I made a new model from scratch. I made sure the model was perfect before I ran the project for the first time.
In WindowControllerDidLoadNib: The project calls a method to add data. Before addData routine, I log the ManagedObjectContext and the ManagedObjectModel. Neither of them are nil.
I am still getting this %$&##! error.
Does anyone have any new ideas about this?
EDIT: Could this be because the new untitled document has never been saved? If so, how do you get around that? Can you save an untitled document? Do you really want to?
I had a similar problem a while back on a file import. Since I had full control, I named and saved the document and then I was able to save the context.
As I indicated in the comment above, at least in Mountain Lion you have to save the document at least once before you can save the context. I did some experiments and the small amount of data that I changed was preserved by autosave without saving the context. I have changed my saveContext method to the following:
- (void)saveContext {
if (![self fileURL]) {
NSLog(#"Can't save context. No file name has been set.");
return;
}
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
[NSApp presentError:error];
NSLog(#"Error userInfo: %#",[error userInfo]);
abort();
}
}

GCD network requests failing on iOS 4

I have the following code in my application to load some data from my API. It works fine, great in fact in iOS 5 but on iOS 4 I am getting so many responses with status 204.
This only happens on iOS 4, this could have been treated as an API error, but it works great in the browser, on Rested.app, on iOS 5 etc... only iOS 4 fails, it fails in the simulator and on the device (iPhone 4).
I am calling this code each time I load a cell into a table view. I have a core data object with a load state, set to no initially, if it's not loaded I perform this code, if it's loaded, I skip this code. In the mean time I display a spinner inside the cell on the table view.
I am sure it's a problem with multiple requests in GCD on iOS 4.
Can anyone spot anything wrong with my code snippet?
-(void)myFunction{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// query users participations (Network)
NSError * _urlError = nil;
NSString * url = [NSString stringWithFormat:#"my api url"];
NSMutableURLRequest * loginHTTPRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
[loginHTTPRequest setValue:#"application/json" forHTTPHeaderField:#"Accept"];
NSLog(#"Description: %#", [loginHTTPRequest description]);
NSHTTPURLResponse * _responseHeaders = nil;
NSData * responseData = [NSURLConnection sendSynchronousRequest:loginHTTPRequest
returningResponse:&_responseHeaders
error:&_urlError];
if(_urlError != nil){
dispatch_async( dispatch_get_main_queue(), ^{
// alert network connection error
});
return;
}
NSString *json_string = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSDictionary * jsonData = [NSDictionary dictionaryWithDictionary:[parser objectWithString:json_string]];
[json_string release];
[parser release];
dispatch_async( dispatch_get_main_queue(), ^{
// here [_responseHeaders statusCode] keeps returning 204 and there is nothing in responseData
// do some Core Data stuff
});
});
}
UPDATE
Note this code is working fine if called even with a for loop repeatedly, the issue is when I invoke this method from tableView:cellForRowAtIndexPath:
I have Core Data objects with a property "isLoaded" set to NO and changed to YES upon remote load. When my tableview's datasource loads the cells for each object, the tableView:cellForRowAtIndexPath: method calls this function if the object's "isLoaded" property is NO.
I suspected the problem may be because there 2 or more simultaneous calls to the API happening when the table is loaded and reloaded. Each successful load from the api invokes reloadData for that tableview.
This lets me have a pre filled tableview with spinners and asynchronously load in my data as I need it on screen which is nice because I can efficiently use NSFetchedResultsController with lazy loading my objects core data.
(I have an endpoint for all my objects returning an array of object id's - I create Core Data objects with only the ID's, all rest of data, name, date etc etc... is not loaded until it's needed).
When I start scrolling around the new cells which are created/reused call this method and they always get a 200 response with the data. it's only the first loading which causes this "block".
I think I found the problem, I was performing synchronous requests on an asynchronous GCD thread, and for some reason timeouts were occurring, but only on requests in iOS 4, maybe the headers are sent slightly differently from iOS 4 which is causing the API to take longer to respond ? Or maybe multiple (as in simultaneous to the millisecond) requests sent from different threads synchronously on an asynchronous thread were clashing in the system before being sent ?
Anyhow... this didn't seem to be the case calling google.com or even my own private server, so it must be something to do with the headers and multiple requests...
I am using asi http from github and it's working a lot more efficiently, now I am not using GCD for these requests, just an ASI queue.
Any final thoughts on iOS 4 synchronous requests performing on an asynchronous GCD thread with possible timeouts not being respected and returning early with a status 204 ?
Ok - if it is https, then you probably can't get away with using sendSynchronousRequest. The documentation states that things like [NSURLConnection connection:didReceiveAuthenticationChallenge:] won't call some key things, i.e.:
If authentication is required in order to download the request, the required credentials must be specified as part of the URL. If authentication fails, or credentials are missing, the connection will attempt to continue without credentials.
I'm still surprised it's working on iOS5, to be honest. I think you'll have to use asynchronous methods to at least debug it to find out what is going on.

Resources