I have followed this article to the letters on the sample app it works fine but in my existing project same code is not doing what it suppose to do. Here's the code:
ParentService:
-(MSCoreDataStore *)store{
if (_store == nil) {
NSManagedObjectContext * context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.parentContext=[[DataBaseDao sharedDao] managedObjectContext];
_store = [[MSCoreDataStore alloc] initWithManagedObjectContext:context];
}
return _store;
}
-(ParentService *) init
{
self = [super init];
if (self) {
// Initialize the Mobile Service client with your URL and key
self.client = [MSClient clientWithApplicationURLString:kApplicationURL
applicationKey:kApplicationKey];
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = delegate.managedObjectContext;
MSCoreDataStore *store = [[MSCoreDataStore alloc] initWithManagedObjectContext:context];
self.client.syncContext = [[MSSyncContext alloc] initWithDelegate:nil dataSource:store callback:nil];
}
return self;
}
Here is the code for SearchService that inherits Parent Service:
static SearchService *singletonInstance;
+ (SearchService*)getInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singletonInstance = [[super alloc] init];
});
return singletonInstance;
}
-(SearchService *) init
{
self = [super init];
if (self) {
_searchTable = [self.client syncTableWithName:#"Search"];
}
return self;
}
In addItem function call to insert occurs with no errors and auto generated id is given with result dictionary and call to syncData occurs but no updates on the server side. Also unable to retrieve data from local store as well. I am unable to verify that record is added to the store or not because .sqlite asks for password.
I have spent several hours on figuring it out but no luck. Odd thing is I am not getting any error which tells me that I might be missing something what that I need to figure out with your help.
Related
I have an app where I download the data on startup using a list of operations and it crashes randomly for unknown core data reasons so I spent few days on checking the best practices to update/fetch data in multithreading core data with MagicalRecord. One of the options was to enable the multithreading debugger -com.apple.CoreData.ConcurrencyDebug 1 where Xcode stops the apps when it violates one of their rules. So, Xcode stops my app on this line [SyncRequestEntity MR_createEntityInContext:[self getPrivateContext]]
+ (MagicalRecordVersionNumber) version
{
return MagicalRecordVersionNumber2_3;
}
#implementation NSManagedObjectContext (MagicalRecord)
+ (NSManagedObjectContext *) MR_context
{
return [self MR_contextWithParent:[self MR_rootSavingContext]];
}
+ (NSManagedObjectContext *) MR_contextWithParent:(NSManagedObjectContext *)parentContext
{
NSManagedObjectContext *context = [self MR_newPrivateQueueContext];
[context setParentContext:parentContext];
[context MR_obtainPermanentIDsBeforeSaving];
return context;
}
- (void) MR_obtainPermanentIDsBeforeSaving
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(MR_contextWillSave:)
name:NSManagedObjectContextWillSaveNotification
object:self];
}
+ (NSManagedObjectContext *) MR_newPrivateQueueContext
{
NSManagedObjectContext *context = [[self alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
MRLogInfo(#"Created new private queue context: %#", context);
return context;
}
#end
#implementation MyClass
- (NSManagedObjectContext *) getPrivateContext
{
if (self.privateContext == nil)
{
self.privateContext = [NSManagedObjectContext MR_context];
}
return self.privateContext;
}
- (SyncRequestEntity *) getSyncRequest
{
SyncRequestEntity *syncRequest = [SyncRequestEntity MR_findFirstByAttribute:#"key" withValue:self.itemKey inContext:[self getPrivateContext]];
// Checking if the entity was sync previously with the same filters.
if (syncRequest == nil)
{
syncRequest = [SyncRequestEntity MR_createEntityInContext: [self getPrivateContext]];
}
return syncRequest;
}
#end
#implementation NSManagedObject (MagicalRecord)
+ (id) MR_createEntityInContext:(NSManagedObjectContext *)context
{
if ([self respondsToSelector:#selector(insertInManagedObjectContext:)] && context != nil)
{
id entity = [self performSelector:#selector(insertInManagedObjectContext:) withObject:context];
return entity;
}
else
{
NSEntityDescription *entity = nil;
if (context == nil)
{
entity = [self MR_entityDescription];
}
else
{
entity = [self MR_entityDescriptionInContext:context];
}
if (entity == nil)
{
return nil;
}
return [[self alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
}
}
#end
The privateContext is a local variable for each operation so I have private contexts for each operation in order to not interrupt the main one. The point is that I create one private context for each thread and I'm just trying to create a new NSManagedObject instance using this context and Xcode says that I'm violating the multithreading core data rules. Does anyone have any clue on what's happening?
We have the same issue when developing our own app.
When you try to perform a write operation in a thread different to the context one, it crash sometimes.
Our solution was to make a private manager on the AppDelegate.m file. Just add this code:
- (NSManagedObjectContext *)getPrivateManagedObjectContext
{
if (self.managedObjectContext != nil) {
return self.managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self getPersistentStoreCoordinator];
if (coordinator != nil) {
self.managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[self.managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return self.managedObjectContext;
}
Then when you need to perform any operation, you should use this method that ensures the block is running on the same thread of the context:
[self.managedObjectContext performBlock:^{...}];
[self.managedObjectContext performBlockAndWait:^{...}];
When I start app and enter some data to Core Data via UITextField, they are displayed correctly in UITableView. Problem is when I restart the app, all data from Core Data is lost... Here is my code.
#synthesize managedObjectContext = _managedObjectContext;
#synthesize managedObjectModel = _managedObjectModel;
#synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
- (void)viewDidLoad {
[super viewDidLoad];
AppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
_managedObjectContext = [appDelegate managedObjectContext];
_managedObjectModel = [appDelegate managedObjectModel];
_persistentStoreCoordinator = [appDelegate persistentStoreCoordinator];
}
- (IBAction)saveButtonPressed:(id)sender {
Truck *truck = [NSEntityDescription insertNewObjectForEntityForName:#"Truck" inManagedObjectContext:_managedObjectContext];
[truck setModel:self.tField.text];
}
Once you create your truck object, you must save the context so that it gets written to the database.
- (IBAction)saveButtonPressed:(id)sender {
Truck *truck = [NSEntityDescription insertNewObjectForEntityForName:#"Truck" inManagedObjectContext:_managedObjectContext];
[truck setModel:self.tField.text];
[_managedObjectContext save:nil]; // you should provide an NSError object
}
First of all, I'd like to say that I'm really having a bad time trying to configure and use my SQLite DB in a background thread so that the main thread is not blocked.
After I found a little guide somewhere on the Internet, I've decided to go for the FMDB wrapper.
All the methods related to the DB operations are in the same class and this is where I'm getting errors:
I've set the static variables like this:
static FMDatabaseQueue *_queue;
static NSOperationQueue *_writeQueue;
static NSRecursiveLock *_writeQueueLock;
Then in my init method I have:
- (id)init {
self = [super init];
if (self) {
_queue = [FMDatabaseQueue databaseQueueWithPath:[self GetDocumentPath]];
_writeQueue = [NSOperationQueue new];
[_writeQueue setMaxConcurrentOperationCount:1];
_writeQueueLock = [NSRecursiveLock new];
}
return self;
}
And this is the method that gives me the error:
- (void)UpdateTime:(NSString *)idT :(int)userId {
[_writeQueue addOperationWithBlock:^{
[_writeQueueLock lock];
[_queue inDatabase:^(FMDatabase *dbase) {
AppDelegate *deleg = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if (![dbase executeUpdate:#"update orari set orario=datetime(orario, '? minutes') where nome=? and dataid>=? and idutente=?"
withArgumentsInArray:#[[NSNumber numberWithFloat:deleg.diff], deleg.nome, [NSNumber numberWithInt:deleg.idMed], [NSNumber numberWithInt: userId]]]) {
NSLog(#"error");
}
}];
[_writeQueueLock unlock];
}];
[_writeQueue addOperationWithBlock:^{
[_writeQueueLock lock];
[_queue inDatabase:^(FMDatabase *dbase) {
AppDelegate *deleg = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if (![dbase executeUpdate:#"UPDATE orari SET presa=1 where dataid=? and idutente=?"
withArgumentsInArray:#[[NSNumber numberWithInt:deleg.identific], [NSNumber numberWithInt: userId]]]) {
NSLog(#"error");
}
}];
[_writeQueueLock unlock];
}];
[self AddNotification];
}
These are the errors I'm getting:
*** -[NSRecursiveLock dealloc]: lock (<NSRecursiveLock: 0xc38b350> '(null)') deallocated while still in use
DB Error: 5 "database is locked"
*** -[NSRecursiveLock unlock]: lock (<NSRecursiveLock: 0x13378d20> '(null)') unlocked when not locked
From the guide I've read, I supposed that the access to my DB would have been "serialized", and each update would have been added to a queue and executed one at a time.
As you can see, I have a lot to learn about this topic, so any help would really be appreciated.
As I can See you have not created shared instance or singleton instance of this init call
- (id)init {
self = [super init];
if (self) {
_queue = [FMDatabaseQueue databaseQueueWithPath:[self GetDocumentPath]];
_writeQueue = [NSOperationQueue new];
[_writeQueue setMaxConcurrentOperationCount:1];
_writeQueueLock = [NSRecursiveLock new];
}
return self;
}
This should be a singleton call as you will create multiple instance of NSOperationQueue which will make DB vulnurable in a multi-threaded environment, try making it singleton call for your database either using GCD or
static DBManager *sharedInstance = nil;
+(DBManager*)getSharedInstance{
if (!sharedInstance) {
sharedInstance = [[super allocWithZone:NULL]init];
_queue = [FMDatabaseQueue databaseQueueWithPath:[self GetDocumentPath]];
_writeQueue = [NSOperationQueue new];
[_writeQueue setMaxConcurrentOperationCount:1];
_writeQueueLock = [NSRecursiveLock new];
}
return sharedInstance;
}
It might solve your problem and this is first time I am answering here and I am new to the environment so please be little bit forgiving :) Thanks
Been trying to create a One to Many relationship with core data with little progress.
Just trying to make a simple core data example.
Entity A = Type:
typeName = String.
Entity B = Routine:
routineName = String.
Relationship: Type has many routines.
Ive got a few view controllers.
TypeViewController (Table view controller to list all the Types from Entity A.
AddTypeViewController (View controller to add a type to the entity through a typeTextField. This is pushed from a add button in the TypeViewController)
RoutineViewController (Table view controller which is pushed when a type is selected from TypeViewController. Lists the routines from Entity B associated with the type selected.
AddRoutineViewController (View controller to add Routine to entity B through routineNameTextField. This is pushed from a add button in the RoutineView Controller.
I started off with implementing just the first Entity and managed to get the First 2 view controllers working. (Was able to add types and view them on the first view controller.
Once i created the second entity and linked the relationship (also the inverse relationship too) the first part stopped working.
Q. Do i have to change the way data is fetched in view controller 1? (As in this view controller i am only fetching data from Entity A).
Q. Is it possible to be adding data separately? (adding type data from the view controller 2. and adding routine data from view controller 4.)
Q. When saving data in view controller 4...
- (IBAction)savePressed:(id)sender {
RoutinePlan *routinePlan = [[RoutinePlan alloc] initWithEntity:[NSEntityDescription entityForName:#"RoutinePlan" inManagedObjectContext:self.managedObjectContext]
insertIntoManagedObjectContext:self.managedObjectContext];
[routinePlan setValue:self.rNameTextField.text forKey:#"rName"];
**How to add this routine to the selected type**
NSError *savingError;
if (![self.managedObjectContext save:&savingError])NSLog(#"Error saving: %#", savingError);
}
Thank you for any help. If any more information is needed i can provide it...
Also does anyone know of a basic example out there of one to many relationships?
EDIT:
***Code snippet from TypeViewController.m
- (NSManagedObjectContext *)managedObjectContext{
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([[segue identifier] isEqualToString:#"current Type Selected"]) {
NSManagedObject *selectedRoutine = [self.routineTypeArray objectAtIndex:[[self.tableView indexPathForSelectedRow] row]];
RoutinePlanViewController *destViewController = segue.destinationViewController;
destViewController.routineTypeObject = selectedRoutine;
}}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
// Fetch the devices from persistent data store
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Type"];
self.routineTypeArray = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
[self.tableView reloadData];}
**Code Snippet from AddTypeViewController.m
- (NSManagedObjectContext *)managedObjectContext {
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;}
- (IBAction)savePressed:(id)sender {
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *newRoutine = [NSEntityDescription insertNewObjectForEntityForName:#"Type" inManagedObjectContext:context];
[newRoutine setValue:self.rTypeTextField.text forKey:#"rType"];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil]; }
**Code Snippet from RoutineViewController.m
#synthesize routineTypeObject;
- (NSManagedObjectContext *)managedObjectContext{
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"current Type Selected"]) {
AddRoutineViewController *destViewController = segue.destinationViewController;
destViewController.routineTypeObject = routineTypeObject;
}
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Fetch the devices from persistent data store
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Routine"];
self.routineArray = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
[self.tableView reloadData];
}
**Code Snippet from AddRoutineViewController.m
#synthesize routineTypeObject;
- (NSManagedObjectContext *)managedObjectContext {
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
- (IBAction)savePressed:(id)sender {
Routine *routinePlan = [[Routine alloc] initWithEntity:[NSEntityDescription entityForName:#"Routine" inManagedObjectContext:self.managedObjectContext]
insertIntoManagedObjectContext:self.managedObjectContext];
[routinePlan setValue:self.rNameTextField.text forKey:#"rName"];
**Error
[Routine.Type = routineTypeObject];
NSError *savingError;
if (![self.managedObjectContext save:&savingError])NSLog(#"Error saving: %#", savingError);
}
Q. Do i have to change the way data is fetched in view controller 1? (As in this view controller i am only fetching data from Entity A).
No, you shouldn't need to (but you've given no information on how you currently do it or what any error is).
Q. Is it possible to be adding data separately? (adding type data from the view controller 2. and adding routine data from view controller 4.)
Yes, as long as you have a reference to the type that is being edited and the managedObjectContext to add to there is no limitation based on the view controller.
Q. When saving data in view controller 4...
You need to add the line that configures the relationship. The easiest way to do that is:
routinePlan.type = type;
(Where routinePlan has the relationship called type and you have the instance to be edited. Using the dot notation also requires that you have the managed object subclass).
I've just setup a UITableview with Core Data and Grand Central Dispatch to update my app and display information through my fetchedResultsController. I have the application updating my database; however, the UITableView only gets populated once I redeploy the application to my phone through Xcode. For instance I run the update and everything works fine except I have an empty UITableView. Then I can close the app and click "Run" again through Xcode and when the app comes up the information is in the UITableView. I'm including the code below in hopes someone can help me discover why this is the case. If I need to include more code please just let me know. Thanks!
TeamTableViewController.m
- (NSFetchedResultsController \*)fetchedResultsController {
...
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchREquest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"stateCode" cacheName:nil];
self.fetchedResultsController = aFetchedResultsController;
...
}
-(IBAction) refreshList:(id)sender {
dispatch_queue_t queue = dispatch_queue_create("updateQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue,^ { [self updateFromXMLFile:#"https://path/to/file.xml"];});
dispatch_async(queue,^ { [self updateFromXMLFile:#"https://path/to/file2.xml"];});
dispatch_async(queue,^ { [self updateFromXMLFile:#"https://path/to/file3.xml"];});
}
- (BOOL)updateFromXMLFile:(NSString *)pathToFile {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
XMLParser *parser1 = [[XMLParser alloc] initXMLParser];
XMLParser *parser2 = [[XMLParser alloc] initXMLParser];
XMLParser *parser3 = [[XMLParser alloc] initXMLParser];
BOOL success = FALSE;
NSURL *url = [[NSURL alloc] initWithString:pathToFile];
NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:url];
if ([pathToFile rangeOfString:#"people"].location != NSNotFound) {
NSManagedObjectContext * peopleMOC = [[NSManagedObjectContext alloc] init];
[peopleMOC setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]];
NSNotificationCenter * notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self selector:#selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object: peopleMOC];
parser1.managedObjectContext = peopleMOC;
[xmlParser setDelegate: parser1];
success = [xmlParser parse];
if (success) {
NSError * error = nil;
#try {
[parser1.managedObjectContext save:&error];
} catch (NSException * exception) {
// NSLog logs the exception...
}
[NSNotificationCenter defaultCenter] removeObserver:self];
Return TRUE;
} else {
// NSLog logs errors
return FALSE;
}
} elseif ... { // other 3 use practically same code here }
[appDelegate saveContext];
}
-(void) mergeChanges:(NSNotification *) notification {
AppDelegate *theDelegate = [[UIApplication sharedApplication] delegate];
[[theDelegate managedObjectContext] performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:) withObject: notification waitUntilDone:YES];
}
UPDATE
I'm able to kind of get it to work by adding [theDelegate saveContext]; to the end of my -(void)mergeChanges method... This just doesn't seem like the proper way of doing it to me. Thoughts?
UPDATE 2
The above method worked one time but I've been unable to get it to replicate.
You should use the NSFetchedResultsControllerDelegate protocol to inform your view controller of any changes in the data.
Use controllerDidChangeContent: rather than the other methods. Like this, the results will be reflected once all the downloads have finished. Shorter incremental updates might get computationally expensive.
There is a very succinct "Typical Use" example in the delegate documentation.