Working in CoreData simultaneously - objective-c

Firstly, sorry if my english is not perfect, i'm not a native english person ;) !
I'm working on an Application which retrieves EKEvent From iCal and add them to my App.
The purpose of the application is a calendar so :
The user retrieve iCal's EKEvent from the calendar he wants to.
The EKEvent are saved into an Entity named "Event".
The user can edit, add, delete event from the application - the EKEvent associate will be modified in iCal too.
Issue : when the user modify something in iCal, it has to be modified into my application so the only way i found is to retrieve all EKEvent from iCal - when the app become active - and copy it into a BackUp Entity named "EventBackup". When all EKEvent from iCal are well retrieved and saved into the "EventBackup" entity i copy the entity into my main Entity "Event".
I'm doing it succesfully in async with
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL), ^(void) { });
But I have to keep using my application - so retrieving Event * from CoreData - while i'm doing the EventBackup... problem my application crash if i'm working on the CoreData.
Could you help on that way, or propose me something different than the way i'm doing.
Thanks a lot for helping me !

You should look at NSManagedObjectContext's performBlock: method. Specifically you should create a child context, make your changes in it on a background thread and then listen for the save notification to merge it into the parent context.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(managedObjectContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:nil];
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[childContext setParentContext:self.managedObjectContext];
[childContext performBlock:^{
//Do everything here...
NSError *error = nil;
[context save:&error];
if (error) {
NSLog(#"Error saving child context:%#", error.localizedDescription);
}
}];
Listen for the child context to save and then save the main context.
- (void)managedObjectContextDidSave:(NSNotification *)notification
{
if ([notification object] != self.managedObjectContext) {
dispatch_sync(dispatch_get_main_queue(), ^{
[self.managedObjectContext save:nil];
});
}
}

To be more clear about what i'm doing - using your code.
The methods in the block are working on the childContext's NSManagedObjectContext
I'm in the AppDelegate, that
- (void)methodToBeCalledEveryTimeTheAppBecomeActive
{
NSManagedObjectContext *contextParent = [[LavigneCoreData defaultManager] managedObjectContext];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(managedObjectContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:nil];
childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[childContext setParentContext:contextParent];
[childContext performBlock:^{
[self copySpecialsEvents];
[self clearBackUpEntity];
[self getCalendarWhichHasBeenSelected];
[self copyNotesIntoBackUpEntity];
[self clearEntityEvent];
[self deepCopyFromBackUpEntityToEntity];
NSError *error = nil;
[contextParent save:&error];
if (error) {
NSLog(#"Error saving child context:%#", error.localizedDescription);
}
}];
}
i'm getting the error on the line : [childContext setParentContext:contextParent];
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Parent NSManagedObjectContext must use either NSPrivateQueueConcurrencyType or NSMainQueueConcurrencyType.'
EDIT :
i fixed this error by changing
_managedObjectContext = [[NSManagedObjectContext alloc] init];
with
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

Related

AVQueuePlayer (or AVPlayer) with a Soundcloud stream item block the UI (Main Thread)

Thank to those two posts, I resolve this problem during the creation of the AVPLayerItem
AVQueuePlayer playback without gap and freeze
AVPlayer "freezes" the app at the start of buffering an audio stream
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[NSURL URLWithString:urlString] options:#{AVURLAssetPreferPreciseDurationAndTimingKey : #(YES)}];
NSArray *keys = #[#"playable", #"tracks",#"duration" ];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^()
{
for (NSString *thisKey in keys) {
NSError *error = nil;
AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
if (keyStatus == AVKeyValueStatusFailed) {
return ;
}
}
AVPlayerItem *item = [[AVPlayerItem alloc] initWithAsset:asset];
dispatch_async(dispatch_get_main_queue(), ^ {
[item addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionNew context:nil];
[player insertItem:item afterItem:nil];
});
}];
});
But, with a stream from Soundcloud (https://api.soundcloud.com/tracks/146924238/stream?client_id=76395c48f97de903ff44861e230116bd), after the item's status is ready to play (AVPlayerStatusReadyToPlay), the AVQueueplayer keep doing something in the main thread "freezing" the UI (Track keep playing in background thread).
I noticed this problem is more important when the network is low, Then I made the conclusion that this freeze exist when the item is buffering the stream.
Someone have the solution or a trick?

IOS How to sync multithreading NSManagedObjectContext?

Application must update data from WebService in loop each 10 sec in background and display data to user by his request in the main thread. Also I need update and delete records by user request.
Updates done with runloop.
I have registered notification in the AppDelegate
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:nil];
- (void)contextChanged:(NSNotification*)notification
{
if ([notification object] == [self managedObjectContext]) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(contextChanged:) withObject:notification waitUntilDone:YES];
return;
}
[[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
[self saveContext]; //do I need this here or marge save data too?
}
I have Storage sharedInstance class with
NSOperationQueue* operationQueue;
then inserts,updates,selects operators added this way from any thread:
-(void)do_something
{
[self.operationQueue addOperationWithBlock:^{
NSManagedObjectContext*moc; //creating new NSManagedObjectContext with AppDelegate.persistentStoreCoordinator
//do my staff
[moc save:&error]
}]
}
The problem is when I try update entities with #"my_id=%#", #(my_id)
[moc countForFetchRequest:fetchRequest error:&error]
return 0 and cause inserting of duplicate exists entity
The problem is with synchronization.
Advice please.
should I use instance of dispatch_queue_create("com.my.", 0); instead for each CoreData operation?
I did try remove operationQuiue
-(void)query:(void(^)(NSManagedObjectContext *context))queryBlock
{
NSLog(#"query CALL");
__block NSManagedObjectContext *context;
//if remove dispatch_sync and/or run in main thread result the same
dispatch_sync( dispatch_queue_create("com.myapp.db-queue", 0), ^{
AppDelegate*app = AppDelegate();
//same result if I use
//app.persistentStoreCoordinator or
//[app.managedObjectContext persistentStoreCoordinator]
NSPersistentStoreCoordinator *persistentStoreCoordinator= [app.managedObjectContext persistentStoreCoordinator];
context = [NSManagedObjectContext new];
[context setPersistentStoreCoordinator:persistentStoreCoordinator];
[context setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
queryBlock(context);
if ([context hasChanges])
{
NSError*err;
[context save:&err];
if (err) {
NSLog(#"context save: %#",[err localizedDescription]);
}
}
});
}
and call it as :
CoreStorage* cs = [CoreStorage sharedInstance];
NSArray* list = [ws GetSections]; //array of NSDictionaries
//if add this to operationQuiue resunt the same
[cs query:^(NSManagedObjectContext *moc) {
NSLog(#"START");
for (NSDictionary *section in list) {
NSNumber* Id= #([[section objectForKey:#"section_id"] integerValue]);
NSFetchRequest * fetchRequest = [NSFetchRequest new];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Section" inManagedObjectContext: moc];
[fetchRequest setEntity:entity];
[fetchRequest setFetchLimit:1];
[fetchRequest setIncludesSubentities:NO];
[fetchRequest setPredicate: [NSPredicate predicateWithFormat:#"section_id=%#",Id]];
NSError *error =nil;
Section *entry;
if ([moc countForFetchRequest:fetchRequest error:&error] >0)
{
entry = [moc executeFetchRequest:fetchRequest error:nil][0];
NSLog(#"exist"); //this never call
}
else
{
entry = [NSEntityDescription insertNewObjectForEntityForName:#"Section" inManagedObjectContext:moc];
NSLog(#"NEW");
}
entry.section_id = Id;
entry.timeStamp = [NSDate date];
}
}];
Any sugastions please?
The problem is probably in the operation queue. You haven't configured it with max concurrent operations to be 1, right? In this case it is not serial, and operations that you add to it run concurrently. So here what happens. First operation fetches for the count of object with some ID, doesn't find it and creates one. At some point before it saves, another operation is added. This second operation fetches for the object with the same ID, doesn't find it and creates one. Then the first operation saves, then the second one saves, and you have a duplicate.
So try to make your operation queue serial [operationQueue maxConcurrentOperationCount:1];.
And no, you don't have to save after calling merge method of the managed object context.

NSFetchedResultsController requires a non-nil fetchRequest and managedObjectContext

i am having a problem since days, i tryed all the solutions here in stackoverflow but anyone gives me a solution.
i am using core data and tableviewcontroller.
My CoreDataTableViewController its correct, i downloaded it from timroadnley and u use it in another projects.
Im getting an error : 'An instance of NSFetchedResultsController requires a non-nil fetchRequest and managedObjectContext'
The application loads correctly but when i press to go to the marksTableviewcontroller, it fails.
THE ERROR IS ON THIS LINE:
- (void)setupFetchedResultsController{
// 1 - Decide what Entity you want
NSString *entityName = #"Marks"; // Put your entity name here
NSLog(#"Setting up a Fetched Results Controller for the Entity named %#", entityName);
// 2 - Request that Entity
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
// 4 - Sort it if you want
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"subject"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)]];
// 5 - Fetch it
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil]; //IN THIS LILE IS THE ERROR, IN "REQUEST"
[self performFetch];
}
THIS IS MY APPDELEGATE:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
marksTVCon.managedObjectContext = self.managedObjectContext;
NSDictionary *defaultsDict = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithBool:YES], #"FirstLaunch", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsDict];
NSUserDefaults *sharedDefaults = [NSUserDefaults standardUserDefaults];
if([sharedDefaults boolForKey:#"FirstLaunch"]) {
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"MYAPP" message:#"Thanks for downloading, hope you love our app" delegate:nil cancelButtonTitle:#"Go app" otherButtonTitles:nil, nil];
[message show];
[sharedDefaults setBool:NO forKey:#"FirstLaunch"];
[sharedDefaults synchronize];
}
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Saves changes in the application's managed object context before the application terminates.
[self saveContext];
}
- (void)saveContext
{
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
}
#pragma mark - Core Data stack
// Returns the managed object context for the application.
// If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
// Returns the managed object model for the application.
// If the model doesn't already exist, it is created from the application's model.
- (NSManagedObjectModel *)managedObjectModel
{
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"MYAPP" withExtension:#"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"MYAPP.sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
Typical reasons for an error here include:
* The persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
#{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES}
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
#pragma mark - Application's Documents directory
// Returns the URL to the application's Documents directory.
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
#end
If you need to ask for anyother peace of code pliz ask for it, I think the problem is in there, but im not pretty Sure.

Where to create a NSManagedObjectContext for an NSInvocationOperation

I have multiple NSInvocationOperations created and added to an NSOperationQueue. Two of these
NSInvocationOperations create lots of objects of the same parent class (Country and City which subclass Location). It has mostly gone well except that I've noticed changes to one model or the other are kinda clobbered.
Looking at the store (using a sqlite program) I see the first City (of maybe 200 total) created and then all of the Countries (again maybe 200) created. If I delete the app and run it again I'll see the first Country and then all of the Cities.
I hit the docs and noticed that Apple suggestions setting up your per thread MOCs in the start method of you NSOperation. However I'm not using an NSOperation, I'm using an NSInvocationOperation. It's actually making me question more so why they suggest creating your MOC in start.
This is my selector for my NSInvocationOperation...
+ (void)load:(NSString *)file
{
NSManagedObjectContext *managedObjectContext = [(OSSMAppDelegate *)[[UIApplication sharedApplication] delegate] adHocManagedObjectContext];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:managedObjectContext];
SBJsonParser *jsonParser = [[SBJsonParser alloc] init];
NSString *json = [[NSString alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:file ofType:#"json"]];
NSArray *objects = [[jsonParser objectWithString:json] valueForKeyPath:#"objects"];
for(NSDictionary *object in objects)
{
[self createObjectWithObject:object inManagedObjectContext:managedObjectContext];
}
NSError *error = nil;
[managedObjectContext save:&error];
}
...from the app delegate...
- (NSManagedObjectContext *)adHocManagedObjectContext
{
NSManagedObjectContext *adHocManagedObjectContext = nil;
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
adHocManagedObjectContext = [[NSManagedObjectContext alloc] init];
[adHocManagedObjectContext setPersistentStoreCoordinator:coordinator];
[adHocManagedObjectContext setUndoManager:nil];
}
return adHocManagedObjectContext;
}
...then somewhere else (Note: firstRun calls load:)...
NSInvocationOperation *countryInvocationOperation = [[NSInvocationOperation alloc] initWithTarget:[Country class] selector:#selector(firstRun) object:nil];
[operationQueue addOperation:countryInvocationOperation];
Is there any problem with creating the MOC in the selector that's being invoked? I'd image it has to be since the MOC is tied to the thread it's created on. I guess any pointers as to where I'm going wrong is helpful.
I'm not sure I understand your problem (Do you have missing countries or cities?, do you have incorrect order? give an example of 'clobbered').
As for your question:
Is there any problem with creating the MOC in the selector that's being invoked?
No, there is no problem. the documentation only say it must be created ON the thread you intend to use it (start and main are methods that will run on the operation thread). hence, NSInvocationOperation will run your method in the operation thread, and you can create your MOC there without worries.

Passing delegate through another object with ARC

I've got 2 classes, MPRequest and MPModel.
The MPModel class has a method to lookup something from the core data store, and if not found, creates an MPRequest to retrieve it via a standard HTTP request (The method in MPModel is static and not and instance method).
What I want is to be able to get a progress of the current HTTP request. I know how to do this, but I'm getting a little stuck on how to inform the view controller. I tried creating a protocol, defining a delegate property in the MPRequest class, altering the method in MPModel to accept this delegate, and in turn passing it to the MPRequest when it is created.
This is fine, however ARC is then releasing this delegate whilst the request is running and thus doesn't do what I want. I'm trying to avoid making my delegate object a strong reference in case it throws up any reference cycles but I don't know any other way of doing this.
To start the request, from my view controller I'm running
[MPModel findAllWithBlock:^(NSFetchedResultsController *controller, NSError *error) {
....
} sortedBy:#"name" ascending:YES delegate:self]
Inside the findAllWithBlock method, I have
MPRequest *objRequest = [MPRequest requestWithURL:url];
objRequest.delegate = delegate;
[objRequest setRequestMethod:#"GET"];
[MPUser signRequest:objRequest];
[objRequest submit:^(MPResponse *resp, NSError *err) {
...
}
And in the MPRequest class I have the following property defined :
#property (nonatomic, weak) NSObject<MPRequestDelegate> *delegate;
Any ideas or suggestions?
As requested, here is some more code on how things are being called :
In the view controller :
[MPPlace findAllWithBlock:^(NSFetchedResultsController *controller, NSError *error) {
_placesController = controller;
[_listView reloadData];
[self addAnnotationsToMap];
[_loadingView stopAnimating];
if (_placesController.fetchedObjects.count > 0) {
// We've got our places, but if they're local copies
// only, new ones may have been added so just update
// our copy
MPSyncEngine *engine = [[MPSyncEngine alloc] initWithClass:[MPPlace class]];
engine.delegate = self;
[engine isReadyToSync:YES];
[[MPSyncManager sharedSyncManager] registerSyncEngine:engine];
[[MPSyncManager sharedSyncManager] sync];
}
} sortedBy:#"name" ascending:YES delegate:self];
Here, self is never going to be released for obvious reasons, so I don't see how this is the problem.
Above, MPPlace is a subclass of MPModel, but the implementation of the findAllWithBlock:sortedBy:ascending:delegate: is entirely in MPModel
The method within MPModel looks like this
NSManagedObjectContext *context = [[MPCoreDataManager sharedInstance] managedObjectContext];
[context performBlockAndWait:^{
__block NSError *error;
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([self class])];
[request setSortDescriptors:#[[[NSSortDescriptor alloc] initWithKey:key ascending:asc]]];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:nil
cacheName:nil];
[controller performFetch:&error];
if (!controller.fetchedObjects || controller.fetchedObjects.count == 0) {
// Nothing found or an error, query the server instead
NSString *url = [NSString stringWithFormat:#"%#%#", kMP_BASE_API_URL, [self baseURL]];
MPRequest *objRequest = [MPRequest requestWithURL:url];
objRequest.delegate = delegate;
[objRequest setRequestMethod:#"GET"];
[MPUser signRequest:objRequest];
[objRequest submit:^(MPResponse *resp, NSError *err) {
if (err) {
block(nil, err);
} else {
NSArray *objects = [self createListWithResponse:resp];
objects = [MPModel saveAllLocally:objects forEntityName:NSStringFromClass([self class])];
[controller performFetch:&error];
block(controller, nil);
}
}];
} else {
// Great, we found something :)
block (controller, nil);
}
}];
The delegate is simply being passed on to the MPRequest object being created. My initial concern was that the MPRequest object being created was being released by ARC (which I guess it probably is) but it didn't fix anything when I changed it. I can't make it an iVar as the method is static.
The submit method of the request looks like this :
_completionBlock = block;
_responseData = [[NSMutableData alloc] init];
[self prepareRequest];
[self prepareRequestHeaders];
_connection = [[NSURLConnection alloc] initWithRequest:_urlRequest
delegate:self];
And when the app starts downloading data, it calls :
[_responseData appendData:data];
[_delegate requestDidReceive:(float)data.length ofTotal:_contentLength];
Where _contentLength is simply a long storing the expected size of the response.
Got it working. It was partly an issue with threading, where the core data thread was ending before my request, me looking at the output from a different request entirely, and the way ARC handles memory in blocks.
Thanks for the help guys