I'm using MagicalRecord and I can not understand with and retrieve the id of the record you just saved
Items *item = [Items MR_createEntity];
item.ref_user = ref_user;
[self saveContext];
- (void)saveContext {
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
if (success) {
DDLogInfo(#"MR_saveContext success");
[self loadView];
[self viewDidLoad];
} else if (error) {
DDLogError(#"Error saving context: %#", error.description);
}
}];
}
Why not just create it out of block?
item = [ITEM MR_createEntityInContext:defaultContext];
[defaultContext MR_saveToPersistentStoreAndWait];
// you can retrieve the id of above item here
Related
For concurrency in coredata I am using parent-child MOC concept where parent context is of NSMainQueueConcurrencyType and child context is of NSPrivateQueueConcurrencyType and below is the code to
- (NSManagedObjectContext *)managedObjectContext
{
if (managedObjectContext != nil) {
return managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];// DONE to avoid crash when the app auto logs out
managedObjectContext.persistentStoreCoordinator = coordinator;
}
return managedObjectContext;
}
-(NSManagedObjectContext *)getPrivateMOC
{
NSManagedObjectContext *private = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[private setParentContext:[self managedObjectContext]];
return private;
}
-(void)storeCustomerData:(NSArray *)custData
{
NSManagedObjectContext *currentbgContext = [self getPrivateMOC];
[currentbgContext performBlock:^{
for (NSDictionary *jsonObject in custData) {
CustomerEntity *custEntity = [NSEntityDescription insertNewObjectForEntityForName:#"CustomerEntity" inManagedObjectContext:currentbgContext];
custEntity.fname = [jsonObject field:#"fname"];
custEntity.lname = [jsonObject field:#"lname"];
}
NSError *error = nil;
if (![currentbgContext save:&error]) {
NSLog(#"Error saving context: %#\n%#", [error localizedDescription], [error userInfo]);
//abort();
}
}];
}
If I use parent context in method storeCustomerData it works but using child context getPrivateMOC() doesn't.
Please let me know if I am missing anything
I guess I understood your problem, Please try the below code
// Used to propegate saves to the persistent store (disk) without blocking the UI
- (NSManagedObjectContext *)masterManagedObjectContext {
if (_masterManagedObjectContext != nil) {
return _masterManagedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_masterManagedObjectContext.retainsRegisteredObjects = YES; //TO-DO : Need update this later
[_masterManagedObjectContext performBlockAndWait:^{
[_masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
// [_masterManagedObjectContext setUndoManager:nil];
}];
}
return _masterManagedObjectContext;
}
// Return the NSManagedObjectContext to be used in the background during sync
- (NSManagedObjectContext *)backgroundManagedObjectContext {
if (_backgroundManagedObjectContext != nil) {
return _backgroundManagedObjectContext;
}
NSManagedObjectContext *masterContext = [self masterManagedObjectContext];
if (masterContext != nil) {
_backgroundManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_backgroundManagedObjectContext performBlockAndWait:^{
[_backgroundManagedObjectContext setParentContext:masterContext];
}];
}
return _backgroundManagedObjectContext;
}
// Return the NSManagedObjectContext to be used in the background during sync
- (NSManagedObjectContext *)newManagedObjectContext {
NSManagedObjectContext *newContext = nil;
NSManagedObjectContext *masterContext = [self masterManagedObjectContext];
if (masterContext != nil) {
newContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[newContext performBlockAndWait:^{
[newContext setParentContext:masterContext];
}];
}
return newContext;
}
- (void)saveMasterContext {
[self.masterManagedObjectContext performBlockAndWait:^{
NSError *error = nil;
BOOL saved = [self.masterManagedObjectContext save:&error];
if (!saved) {
// do some real error handling
//NSLog(#"Could not save master context due to %#", error);
}
}];
}
- (void)saveBackgroundContext {
[self.backgroundManagedObjectContext performBlockAndWait:^{
NSError *error = nil;
BOOL saved = [self.backgroundManagedObjectContext save:&error];
if (!saved) {
// do some real error handling
//NSLog(#"Could not save background context due to %#", error);
}
}];
}
static dispatch_queue_t coredata_background_save_queue;
dispatch_queue_t background_save_queue()
{
if (coredata_background_save_queue == NULL)
{
coredata_background_save_queue = dispatch_queue_create("com.magicalpanda.coredata.backgroundsaves", 0);
}
return coredata_background_save_queue;
}
- (void)saveDataInBackgroundWithContext:(void(^)(NSManagedObjectContext *context))saveBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[self saveDataInContext:saveBlock];
});
}
- (void)saveDataInContext:(void(^)(NSManagedObjectContext *context))saveBlock
{
NSManagedObjectContext *context = [[EFCoreDataController sharedInstance] backgroundManagedObjectContext];
[context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[[self objectContext] setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
// [[self objectContext] observeContext:context]; //step 3
// block(context); //step 4
if ([context hasChanges]) //step 5
{
NSError *e;
[[self objectContext] save:&e]; //MagicalRecord will dump errors to the console with this save method
}
}
When should I init my NSFetchedResultsController with AFNetworking? Currently I am doing with this:
[[AFHttpClient sharedClient] GET:#"/admin/stockCategories" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
if (response.statusCode == 200) {
NSArray *results = (NSArray *)responseObject;
if ([results count] > 0) {
for (NSDictionary *obj in results) {
StockCategory *category = [StockCategory MR_createEntity];
category.name = obj[#"name"];
category.categoryId = obj[#"_id"];
category.createdDate = [NSDate dateForRFC3339DateTimeString:obj[#"createdDate"]];
[context MR_saveWithOptions:MRSaveParentContexts completion:^(BOOL success, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss];
[self.tableView reloadData];
});
}];
}
} else {
}
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}];
I know normally I put execute fetch request in success block after I saved object to core data. Should I put do this:
[context MR_saveWithOptions:MRSaveParentContexts completion:^(BOOL success, NSError *error) {
// add NSFetchedResultsController here ?
self.fetchedResultsController = [StockCategory MR_fetchAllSortedBy:#"createdDate" ascending:NO withPredicate:nil groupBy:nil delegate:self];
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss];
[self.tableView reloadData];
});
}];
if so, self.fetchedResultsController will be init multiple times. I don't think so. What I am currently do is this:
- (NSFetchedResultsController *)fetchedResultsController{
if (!_fetchedResultsController) {
_fetchedResultsController = [StockCategory MR_fetchAllSortedBy:#"createdDate" ascending:NO withPredicate:nil groupBy:nil delegate:self];
}
return _fetchedResultsController;
}
But in my UITableViewDatasource methods, only numberOfRows method works, but the cell.textLabel.text is empty. But if I relaunch the simulator, and comment the afnetworking get method. everything works, objects have been saved the core data, and NSFetchedResultsController works too.
I'm trying to create an update function that also allows the user to cancel the process.
I'm using the parent-child managedObjectContext and I've set the parent contest concurrency to NSMainQueueConcurrencyType. My plan was to not call the save for parent context to cancel the update. When I tested my theory and commented out the said save lines, I found out the managedObject was still updated. What am I doing wrong?
Partial update function code:
NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[bgContext setParentContext:self.managedObjectContext];
[bgContext setUndoManager:nil];
[bgContext performBlockAndWait:^{
for (NSDictionary *itemDictionary in items) {
//update cancelled
if (status == -1) {
return;
}
//Function to get a single managedObject by querying the main context.
NSArray *array = [self queryEntity:entityName withResId:[dictionary objectForKey:#"res_id"]];
//get the object id of queried managedobject; context -> mainContext
ENTITY *object = (ENTITY *)[context objectWithID:[[array firstObject] objectID]];
object.data = #"something"; //change data
count++;
//save every 100
if(count%100 == 0) {
[bgContext performBlockAndWait:^{
NSError *error;
if(![bgContext save:&error]) {
NSLog(#"insert error child: %#", [error localizedDescription]);
}
}];
}
}
//save excess
if(count%100 != 0) {
[bgContext performBlockAndWait:^{
NSError *error;
if(![bgContext save:&error]) {
NSLog(#"insert error child: %#", [error localizedDescription]);
}
}];
}
//comment out to prevent saving to parent
/*[context performBlockAndWait:^{
NSError *error;
if(![context save:&error]) {
NSLog(#"insert error parent: %#", [error localizedDescription]);
}
}];*/
}];
Found my problem. Thanks to this post: https://stackoverflow.com/a/7825536/2260928
I use these lines to ignore changes on cancel:
[bgContext reset];
[context reset];
While trying to learn more about Core Data and multithreading, I wrote an app that fails to save to core data. First some
background. Before my view appears, I create managed context. This happens in
the main thread:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (!self.managedObjectContext) [self useDocument];
}
- (void)useDocument
{
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:MY_DOCUMENT];
UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:url];
if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
[document saveToURL:url
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
if (success) {
NSLog(#"###AMRO--->managedObjectContext has been setup");
self.managedObjectContext = document.managedObjectContext;
[self refresh];
}
}];
} else if (document.documentState == UIDocumentStateClosed) {
[document openWithCompletionHandler:^(BOOL success) {
if (success) {
NSLog(#"###AMRO--->managedObjectContext has been opened");
self.managedObjectContext = document.managedObjectContext;
[self refresh];
}
}];
} else {
NSLog(#"###AMRO--->managedObjectContext has been set");
self.managedObjectContext = document.managedObjectContext;
}
}
When my view appears, I fetch data from a client using this approach. The
reason I do this is because I do not want my view to be blocked as my client
refresh is happening:
dispatch_queue_t fetchQ = dispatch_queue_create("Client Fetch", NULL);
dispatch_async(fetchQ, ^{
[self.managedObjectContext performBlock:^{
dispatch_async(dispatch_get_main_queue(), ^{
[self reload];
dispatch_async(fetchQ, ^{
[self refreshClientInformation];
});
});
}];
});
The call to refreshClientInformation, takes about 2 minutes to retrieve
information, however, I want the view to load in the meantime with whatever I
have. The above approach prevents me
from being able to save to context despite the explicit call to
[self.managedContext save:&error] in refreshClientInformation
after each client data update. refreshPersonalClientInformations executes in the "Client Fetch" thread queue.
- (void) refreshPersonalClientInformation
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"CLIENT"];
request.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"stationId" ascending:YES]];
NSDate *twoHoursAgo = [NSDate dateWithTimeIntervalSinceNow:-2*60*60];
request.predicate = [NSPredicate predicateWithFormat:#"conditionsUpdated < %#", twoHoursAgo];
NSError *error = nil;
int counter = 0;
// Execute the fetch
NSArray *matches = [self.managedObjectContext executeFetchRequest:request error:&error];
// Check what happened in the fetch
if (!matches) { // nil means fetch failed;
NSLog(#"ERROR: Fetch failed to find CLIENT %# in database", DEFAULT);
} else if (![matches count]) { // none found, so lets retrieve it below
return;
} else {
for (CLIENT *client in matches) {
NSDictionary *clientDict = [ClientFetcher clientInfo:client.clientId];
client.name = [clientDict[NAME] description];
client.age = [clientDict[AGE] description];
client.updated = [NSDate date];
if (![self.managedObjectContext save:&error]) {
NSLog(#"##########################AMRO Unresolved error %#, %#", error, [error userInfo]);
}
//Have to rate limit requests at no more than 10 per minute.
NSLog(#"Sleeping for 6s");
sleep(6);
NSLog(#"Done sleeping for 6s");
}
}
}
I even tried to run the save in refreshPersonalClientInfo by doing this:
dispatch_async(dispatch_get_main_queue(), ^{
[self.managedObjectContext save:nil];
});
But that did not work, no errors were detected
As an experiment I changed the thread call to this when my view appears and my
core data eventually gets saved, the problem is that my view is blocked and I have to
wait a long time while the data is getting retrieved. Also when I say eventually, I mean that it does not happen after I call [self.managedContext save:nil], but after a few minutes:
dispatch_queue_t fetchQ = dispatch_queue_create("Client Fetch", NULL);
dispatch_async(fetchQ, ^{
[self.managedObjectContext performBlock:^{
[self refreshPersonalClientInformation];
dispatch_async(dispatch_get_main_queue(), ^{
[self reload];
});
}];
});
Any suggestions on how I should retrieve my informaion in a background thread
and be able to save the data to disk while I still leave my UI accessible?
You're better to create a child context to perform everything in the background:
dispatch_queue_t fetchQ = dispatch_queue_create("Client Fetch", NULL);
dispatch_async(fetchQ, ^{
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.parentContext = self.managedObjectContext;
[context performBlock:^{
[self refreshPersonalClientInformation];
dispatch_async(dispatch_get_main_queue(), ^{
[self reload];
});
}];
});
I want to remove all rows in my table view before reloading the data, but can't seem to get it to work.
In my viewcontroller I get my array from this AFNetworking request.
- (void)viewDidLoad
[[LocationApiClient sharedInstance] getPath:#"locations.json"
parameters:nil
success:
^(AFHTTPRequestOperation *operation, id response) {
NSLog(#"Response: %#", response);
NSMutableArray *location_results = [NSMutableArray array];
for (id locationDictionary in response) {
Location *location = [[Location alloc] initWithDictionary:locationDictionary];
[location_results addObject:location];
}
self.location_results = location_results;
[self.tableView reloadData];
}
failure:
^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error fetching locations!");
NSLog(#"%#", error);
}];
}
And I want to remove data then reload it with this button
- (IBAction)locationPressed:(id)sender {
[[Location sharedSingleton].locationManager startUpdatingLocation];
[self viewDidLoad];
NSMutableArray *location_results = [NSMutableArray array];
[location_results removeAllObjects];
[self.tableView reloadData];
}
But it's not removing the rows. I see the reload happening over the top of the rows that are already there. Any suggestions?
Please DON'T EVER call viewDidLoad manually.
Create a method like
- (void)reloadDataWithCompletion:(void(^)(NSArray *locations))completion
failure:(void(^)(NSError *error))failure {
[[LocationApiClient sharedInstance] getPath:#"locations.json"
parameters:nil
success:
^(AFHTTPRequestOperation *operation, id response) {
NSLog(#"Response: %#", response);
NSMutableArray *location_results = [NSMutableArray array];
for (id locationDictionary in response) {
Location *location = [[Location alloc] initWithDictionary:locationDictionary];
[location_results addObject:location];
}
if(completion)
completion(location_results);
}
failure:
^(AFHTTPRequestOperation *operation, NSError *error) {
if(failure)
failure(error);
}];
}
And call it whenever you need to reload the data
- (void)viewDidLoad {
[super viewDidLoad]; // Don't forget the call to super!
[self reloadDataWithCompletion:^(NSArray *locations) {
self.location_results = locations;
[self.tableView reloadData];
} failure:^(NSError *error) {
NSLog(#"Error fetching locations!");
NSLog(#"%#", error);
}];
}
- (IBAction)locationPressed:(id)sender {
[[Location sharedSingleton].locationManager startUpdatingLocation];
[self reloadDataWithCompletion:^(NSArray *locations) {
self.location_results = locations;
[self.tableView reloadData];
} failure:^(NSError *error) {
NSLog(#"Error fetching locations!");
NSLog(#"%#", error);
}];
}
In order to achieve a graphical effect for reloading the table you can do (assuming that you have only one section), substitute
[self.tableView reloadData];
with
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];