iOS queuing the upload tasks - objective-c

In iOS I need to call same method number of time, but the app has to wait until the method completed it's first task. (I can not use bool value to check whether the function is running or not).
How can I queue the request or wait until it's previous task has finished? Is there a way I can use NSOperation or NSThread?
Thanks
This is my method I need to call multiple times
- (void)fetchJobs
{
dispatch_queue_t queueLocal = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t groupLocal = dispatch_group_create();
UIApplication *app = [UIApplication sharedApplication];
NSTimeInterval backgroundTime = app.backgroundTimeRemaining;
NSLog(#"Background task remain time: %g seconds", (double)backgroundTime);
[[NSNotificationCenter defaultCenter] postNotificationName:#"UPLOAD_PROCESS_START" object:nil];
self.assetUpdateTaskID = [app beginBackgroundTaskWithExpirationHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
[self endTask];
});
}];
dispatch_group_async(groupLocal, queueLocal, ^{
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Files" inManagedObjectContext:self.managedObjectContext];
//Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat: #"uploaded like %#", [NSString stringWithFormat:#"0"]];
[request setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
NSError *error = nil;
//fetch the records and handle an error
NSArray *fetchResults = [self.managedObjectContext executeFetchRequest:request error:&error];
if (fetchResults == nil) {
NSLog(#"File Data fetching error : %#", [error localizedDescription]);
}
else{
if ([fetchResults count] > 0) {
dispatch_apply([fetchResults count], queue, ^(size_t i){
NSLog(#"Loop count %zu", i);
Files *file = [fetchResults objectAtIndex:i];
self.manageObjectForFiles = file;
NSLog(#"Fetched File details -> Name: %# & status: %#" , file.fileName, file.uploaded);
BOOL uploaded = [self filePosting:file.fileName];
if (uploaded) {
[self.managedObjectContext deleteObject:file];
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(#"File table update error : %#", [error description]);
}
}
});
/*
for (int i = 0; i < [fetchResults count]; i++) {
}*/
}
}
dispatch_async(dispatch_get_main_queue(), ^{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
int remainJobs = [self fetchFileTableCount];
if (remainJobs > 0) {
[UIApplication sharedApplication].applicationIconBadgeNumber = remainJobs;
[[NSNotificationCenter defaultCenter] postNotificationName:#"UPLOAD_REMAIN" object:nil];
}
else{
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
[[NSNotificationCenter defaultCenter] postNotificationName:#"UPLOAD_PROCESS_COMPLETED" object:nil];
NSLog(#"-----------------------------------------------");
NSLog(#"There are no more uploads");
NSLog(#"-----------------------------------------------");
}
[self endTask];
});
});
dispatch_release(groupLocal);
}

You'd didn't say if the calls have to be serial of if they can be concurrent. If concurrent you can use NSOperation's mainQueue, suspend it, add all but the first call, then in your method, at the end, see if the queue is suspended and if so resume it.
With Grand Central Dispatch (CGD) you can more easily create a serial dispatch queue, and sort of do the same - suspend it, add all but the first call wrapped in a block, then at the end of the first call unsuspend the queue. If you need this to run on the main thread you can have the serial queue tied to the main GCD queue.

Related

Return data after AFNetworking is done

I got a async problem with my code. I got all of my webrequests in 1 class. One of my requests needs to return an NSMutableArray that another class needs to use. My webRequest code is here:
- (NSMutableArray*) getTournamentsInClub:(NSString *)clubGUID withDelegateViewController:(UIViewController *)viewController {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSMutableArray *responseArray = [[[NSMutableArray alloc] init] autorelease];
NSString *URL = [[NSString alloc]initWithFormat:#"SomeURL=%#",clubGUID];
[manager POST:URL parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
for (id obj in responseObject){
//NSLog(#"obj: %#",[obj valueForKey:#"CustomerName"]);
[responseArray addObject:obj];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
return responseArray;
}
I call the method from a viewController like this:
[self handleClubTournaments:[[TournamentsWebService sharedToursWS] getTournamentsInClub:
//Show load screen. (hide in handler function)
GP_MobilAppDelegate *xdelegate = [[UIApplication sharedApplication] delegate];
[xdelegate showLoadingScreen:self.clubToursTableView andStatus:NSLocalizedString(#"loadTours", #"")];
And my handleClubTournaments function looks like this:
-(void) handleClubTournaments:(id)result {
GP_MobilAppDelegate *xdelegate = [[UIApplication sharedApplication] delegate];
if([result isKindOfClass: [NSError class]]) {
// If an error has occurred, handle it
[xdelegate hideLoadingScreen];
[[TournamentsWebService sharedToursWS] showErrorMessageAccordingToFault:result];
return;
}
if([result isKindOfClass: [SoapFault class]]) {
[xdelegate hideLoadingScreen];
// If a server error has occurred, handle it
[[TournamentsWebService sharedToursWS] showErrorMessageAccordingToFault:result];
return;
}
//Do something with result...
if ([result count] > 0) {
NSLog(#"Antal klubturneringer: %d", [result count]);
//Start by removing excisting tours
[self.tournamentsSourceArray removeAllObjects];
NSMutableArray *tempArray=[NSMutableArray array];
for (GGTournamentData *t in result) { //cast object in result list and add them to array
[tempArray addObject:t];
}
self.tournamentsSourceArray = [self sortByStringDate:tempArray]; //sort by date
[tempArray release];
NSLog(NSLocalizedString(#"tourLoadet", #""));
}
[self.clubToursTableView reloadData];
[xdelegate hideLoadingScreen];
//Scroll view
if (self.tournamentsSourceArray.count > 0) { //hvis det er turneringer..
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:[self findIndexOfMonthClosestToDate]];
[self.clubToursTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
}
So my problem is that the NSMutableArray gets returned before my async task is done. I know a async task behaves like that, but how do i make sure that my handleClubTournaments function don't run before my webrequest(getTournamentsInClub) got some data for it?
Thanks in advance.
I don't think you know how Asynchronous operations work. The NSMutableArray will never be set, because it is returned synchronously.
In your case, I suggest you to work with delegates.
- (void)getTournamentsInClub:(NSString *)clubGUID withDelegateViewController:(UIViewController *)viewController completionBlock:(void (^)(NSMutableArray *result))completionBlock {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSMutableArray *responseArray = [[[NSMutableArray alloc] init] autorelease];
NSString *URL = [[NSString alloc]initWithFormat:#"SomeURL=%#",clubGUID];
[manager POST:URL parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
for (id obj in responseObject) {
[responseArray addObject:obj];
}
// Request finished. Call the block.
completionBlock(responseArray);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
- (void)handleClubTournaments {
GP_MobilAppDelegate *xdelegate = [[UIApplication sharedApplication] delegate];
[[TournamentsWebService sharedToursWS] getTournamentsInClub:^(NSMutableArray *result)
{
// Hide the Loading Indicator. Do something with the Result.
}];
// You can't access the result synchronously, therefore it's impossible to depend on it synchronously.
}
another way to return the data asynchronously would be blocks, similar to the AFNetworking solution.
You can read more about getting started with blocks here and how to use delegates here.

Core Data NSInternalInconsistencyException on single background thread delete function

My project has a database handler and one of its function is delete almost all records from the database. I execute the actual deleting on a single background thread. Sometimes my code works fine sometimes it crashes with a NSInternalInconsistencyException. Currently it fails 1 out of 3 tries. Why am I getting a NSInternalInconsistencyException? I thought this only happens when your multi-threading your core data function. Here's my code:
//Database handler init.
- (id)init
{
self = [super init];
if(self) {
self.appDelegate = [UIApplication sharedApplication].delegate;
//getting the managedobjectcontext from appdelegate
self.managedObjectContext = self.appDelegate.managedObjectContext;
//background queue
self.backgroundQueue = dispatch_queue_create("database.queue", NULL);
status = 0;
}
return self;
}
//delete majority of the database entry
- (void) clearTables
dispatch_async(self.backgroundQueue, ^(void) {
NSError *error;
//update this entity records.
for(Entity1 *object in [self queryEntity:#"Entity1"]) {
object.download_ymdhms = nil;
[self.managedObjectContext save:nil];
}
[self.managedObjectContext save:&error];
NSLog(#"error1: %#", [error localizedDescription]);
//Delete the other entities data.
for(Entity2 *object in [self queryEntity:#"Entity2"]) {
[self.managedObjectContext deleteObject:object];
}
[self.managedObjectContext save:&error];
NSLog(#"error2: %#", [error localizedDescription]);
for(Entity3 *object in [self queryEntity:#"Entity3"]) {
[self.managedObjectContext deleteObject:object];
}
...
for(Entity10 *object in [self queryEntity:#"Entity10"]) {
[self.managedObjectContext deleteObject:object];
}
[self.managedObjectContext save:&error];
NSLog(#"error3: %#", [error localizedDescription]);
//notify deletion complete
[[NSNotificationCenter defaultCenter] postNotificationName:#"database.queue.delete.done" object:nil];
});
}
//get all the objects in an entity.
- (NSArray *) queryEntity: (NSString *)entity {
return [self queryEntity:entity withPredicate:nil withLimit:0 orderBy:nil];
}
...
//general query
- (NSArray *) queryEntity: (NSString *)entity withPredicate: (NSString *)predicate withLimit: (int)limit orderBy: (NSString *)order
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:entity inManagedObjectContext:self.managedObjectContext]];
if(predicate.length > 0) {
[fetchRequest setPredicate:[NSPredicate predicateWithFormat: predicate]];
}
if(limit > 0) {
[fetchRequest setFetchLimit:limit];
}
if(order.length > 0) {
[fetchRequest setSortDescriptors: #[[[NSSortDescriptor alloc] initWithKey:order ascending:YES]]];
}
NSError *error;
NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
return results;
}
This is the error that I sometimes get:
CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. statement is still active with userInfo (null)
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'statement is still active'
Edit: added code comments to make it more readable.

NSManagedObjectContext crashing when accessed on external thread

I'm currently having a threading issue with the managedObjectContext within my application. Currently, I have a background thread running that MUST be in the background, but accesses the managedObjectContext at the same time. Another ViewController calls on the method processAllApplications shown below that then calls checkCompletedApplicationsFor24HourExpiration which then calls getAppsWithStatus. The thread seems to be currently locked causing this operation to halt where the warning below is. I need a way to process this through and am quite a noob when it comes to Core Data. Would anyone be able to advise. I was reading that I may have to create multiple instances of my managedObject and merge them. How would I go about that if that is the case?
AppDelegate:
- (NSManagedObjectContext *)managedObjectContext
{
[__managedObjectContext lock];
if (__managedObjectContext != nil) {
[__managedObjectContext unlock];
return __managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
__managedObjectContext = [[NSManagedObjectContext alloc] init];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
[__managedObjectContext unlock];
return __managedObjectContext;
}
- (NSMutableArray*)getAppsWithStatus:(int)intStatus {
NSLog(#"%i on main thread getAppsWithStatus", [NSThread currentThread].isMainThread);
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Application" inManagedObjectContext:self.managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
// Set example predicate and sort orderings...
NSNumber *status = [NSNumber numberWithInt:intStatus];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"status = %# && username = %#", status, [[NSUserDefaults standardUserDefaults] objectForKey:#"username"]];
#warning FAILS HERE INTO ABYSS
[request setPredicate:predicate];
NSError *error = nil;
NSMutableArray* applications = [[NSMutableArray alloc] initWithArray:[self.managedObjectContext executeFetchRequest:request error:&error]];
for (Application* eachApp in applications)
eachApp.applicationNumber = nil;
[self saveDB];
return applications;
}
- (void)processAllApplications:(id)userInfo {
[self.processApplicationsLock lock];
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"username"] == nil) return; // Not logged in
NSLog(#"processing");
[self checkCompletedApplicationsFor24HourExpiration];
[self alertFor12HourCompletedApplications];
[self alertForExpiredDraftApplications];
if ([DeleteAllDraftApplicationsForCurrentApplicationYear isSatisifiedByDate:[DateTimeFactory currentApplicationDate]]) {
[self deleteExpiredApps];
}
[self performSelector:#selector(sendApplications:) withObject:nil afterDelay:3];
[self.processApplicationsLock unlock];
}
- (void)checkCompletedApplicationsFor24HourExpiration {
NSLog(#"OutboxSender - (void)checkCompletedApplicationsFor24HourExpiration");
NSLog(#"%i on main thread checkCompletedApplicationsFor24HourExpiration", [NSThread currentThread].isMainThread);
NSArray* completedApps = [self getAppsWithStatus:STATUS_COMPLETED];
NSDate* targetDate = [self offsetDate:[DateTimeFactory currentApplicationDate] withDay:-1 withMonth:0 withHour:0];
for (Application* theApplication in completedApps) {
if ([MoveCompletedApplicationToDraftApplicationSpec isSatisfiedByApplication:theApplication cutOffDate:targetDate]) {
NSLog(#"Sending To draft with date: %#", theApplication.submittedDate);
theApplication.status = [NSNumber numberWithInt:STATUS_DRAFT];
[self deleteSignatures:theApplication];
}
}
NSString* message = [NSString stringWithFormat:#"%i completed application/s have been sent to drafts", [completedApps count]];
echo_Alert(#"", message);
[self saveDB];
}
create separate managed object context
+(NSManagedObjectContext *)getManagedObjectContext
{
NSManagedObjectContext *managedObjectContext;
#try {
NSPersistentStoreCoordinator * coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
}
#catch (NSException *exception) {
NSLog(#"Exception occur %#",exception);
}
return managedObjectContext;
Use this separate managed object context in your fetching method,
- (NSMutableArray*)getAppsWithStatus:(int)intStatus {
NSMutableArray * mutableObjects;
NSLog(#"%i on main thread getAppsWithStatus", [NSThread currentThread].isMainThread);
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Application" inManagedObjectContext:[self getManagedObjectContext]]; // Here use separate managed object context
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
// Set example predicate and sort orderings...
NSNumber *status = [NSNumber numberWithInt:intStatus];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"status = %# && username = %#", status, [[NSUserDefaults standardUserDefaults] objectForKey:#"username"]];
#warning FAILS HERE INTO ABYSS
[request setPredicate:predicate];
NSError *error = nil;
NSMutableArray* applications = [[NSMutableArray alloc] initWithArray:[[self getManagedObjectContext] executeFetchRequest:request error:&error]];
NSMutableArray * resultedArray = [applications mutableCopy];
NSMutableArray * objectIds = [[NSMutableArray alloc] initWithCapacity:[resultedArray count]];
for (NSManagedObject *obj in resultedArray) {
[objectIds addObject:obj.objectID];
}
mutableObjects = [[NSMutableArray alloc] initWithCapacity:[objectIds count]];
for (NSManagedObjectID * objectID in objectIds) {
NSManagedObject * obj = [self.managedObjectContext
objectWithID:objectID]; // Here use self.managedObjectContext in which you already created.
[mutableObjects addObject:obj];
}
for (Application* eachApp in mutableObjects)
eachApp.applicationNumber = nil;
[self saveDB];
return mutableObjects;
}

Passing a Value Asynchronously

I have the following method that uses blocks and completion handlers:
// Returns YES if photo is stored in a virtual vacation.
- (BOOL) photoIsOnVacation
{
__block BOOL photoOnFile = NO;
// Identify the documents folder URL.
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSError *errorForURLs = nil;
NSURL *documentsURL = [fileManager URLForDirectory:NSDocumentDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:NO
error:&errorForURLs];
if (documentsURL == nil) {
NSLog(#"Could not access documents directory\n%#", [errorForURLs localizedDescription]);
} else {
// Retrieve the vacation stores on file.
NSArray *keys = [NSArray arrayWithObjects:NSURLLocalizedNameKey, nil];
NSArray *vacationURLs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:documentsURL
includingPropertiesForKeys:keys
options:NSDirectoryEnumerationSkipsHiddenFiles
error:nil];
if (!vacationURLs) photoOnFile = NO;
else {
// Search each virtual vacation for the photo.
for (NSURL *vacationURL in vacationURLs) {
NSError *errorForName = nil;
NSString *vacationName = nil;
[vacationURL getResourceValue:&vacationName forKey:NSURLNameKey error:&errorForName];
[VacationHelper openVacationWithName:vacationName usingBlock:^(UIManagedDocument *vacationDocument) {
NSError *error = nil;
NSManagedObjectContext *moc = vacationDocument.managedObjectContext;
// Build fetch request.
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Photo"];
NSString *currentPhotoID = [self.chosenPhoto objectForKey:FLICKR_PHOTO_ID];
request.predicate = [NSPredicate predicateWithFormat:#"unique = %#", currentPhotoID];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"unique" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
// Execute fetch request.
NSArray *checkPhotos = [moc executeFetchRequest:request error:&error];
if (error) {
NSLog(#"Error searching for photo:%#",error);
} else {
Photo *checkPhoto = [checkPhotos lastObject];
if ([checkPhoto.unique isEqualToString:currentPhotoID]) photoOnFile = YES;
}
}];
if (photoOnFile) break;
}
}
}
return photoOnFile;
}
My problem is that photoOnFile is always false because execution reaches the return before the block that contains the fetch request. I've tried embedding the photoOnFile assignment within dispatch_async(dispatch_get_main_queue(),^{ but that hasn't helped. Any guidance appreciated.
Update: here is the reworked code successfully incorporating Ken's recommended solution:
- (void)checkIfPhotoIsOnVacationAndDo:(void(^)(BOOL photoIsOnVacation))completionBlock
{
__block BOOL photoIsOnVacation = NO;
// Identify the documents folder URL.
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSError *errorForURLs = nil;
NSURL *documentsURL = [fileManager URLForDirectory:NSDocumentDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:NO
error:&errorForURLs];
if (documentsURL == nil) {
NSLog(#"Could not access documents directory\n%#", [errorForURLs localizedDescription]);
} else {
// Retrieve the vacation stores on file.
NSArray *keys = [NSArray arrayWithObjects:NSURLLocalizedNameKey, nil];
NSArray *vacationURLs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:documentsURL
includingPropertiesForKeys:keys
options:NSDirectoryEnumerationSkipsHiddenFiles
error:nil];
if (!vacationURLs) photoIsOnVacation = NO;
else {
// Search each virtual vacation for the photo.
for (NSURL *vacationURL in vacationURLs) {
NSError *errorForName = nil;
NSString *vacationName = nil;
[vacationURL getResourceValue:&vacationName forKey:NSURLNameKey error:&errorForName];
[VacationHelper openVacationWithName:vacationName usingBlock:^(UIManagedDocument *vacationDocument) {
NSError *error = nil;
NSManagedObjectContext *moc = vacationDocument.managedObjectContext;
// Build fetch request.
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Photo"];
NSString *currentPhotoID = [self.chosenPhoto objectForKey:FLICKR_PHOTO_ID];
request.predicate = [NSPredicate predicateWithFormat:#"unique = %#", currentPhotoID];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"unique" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
// Execute fetch request.
NSArray *checkPhotos = [moc executeFetchRequest:request error:&error];
if (error) {
NSLog(#"Error searching for photo:%#",error);
} else {
Photo *checkPhoto = [checkPhotos lastObject];
if ([checkPhoto.unique isEqualToString:currentPhotoID]) {
photoIsOnVacation = YES;
completionBlock(photoIsOnVacation);
}
}
}];
if (photoIsOnVacation) break;
}
completionBlock(photoIsOnVacation);
}
}
}
Asynchronicity tends to spread. Once you make an API asynchronous, all of its callers have to be redesigned to work asynchronously, too. Therefore, a method like your - (BOOL) photoIsOnVacation is untenable because its interface is synchronous – the caller expects to have an answer as soon as the call completes – but the implementation doesn't work that way.
You have to redesign to something like - (void) checkIfPhotoIsOnVacationAndDo:(void(^)(BOOL photoIsOnVacation))block. That takes a block from the caller and invokes the block with the answer when it is known.
This is what semaphores are for:
bool waitForBlockToExecute()
{
__block bool value = false;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// sleep for a bit
sleep(1);
value = true;
// notify that the block is finished
dispatch_semaphore_signal(semaphore);
});
// wait for the semaphore
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore); // clean up the semaphore
return value;
}
Obviously, the dispatch_async block would be replaced with your callback block, but I assume you get the picture from the above code.

Saved Core Data does not persist after app closes 80% of the time

I started dealing with Core Data lately, and in my tests, I've found that about 20% of the time the data actually gets saved to the DB. The rest of the time, it's only saved temporarily, while the app is running. If I restart, the last data I saved gets lost.
Does anyone know what the problem could be?
Here's the code:
//Save data
NSEntityDescription *users = [NSEntityDescription insertNewObjectForEntityForName:#"Users" inManagedObjectContext:document.managedObjectContext];
[users setValue:#"Name Test" forKey:#"name"];
[users setValue:[NSNumber numberWithInt:20] forKey:#"age"];
[users setValue:#"Some Country" forKey:#"location"];
//Debugging
//no error ever shows up
NSError *error;
if(![document.managedObjectContext save:&error]) {
NSLog(#"Error: %#", error);
}
//this is just to show that the problem may not be with my UIManagedDocument (self.document), since the NSLog never gets called.
if(self.document.documentState != UIDocumentStateNormal) {
NSLog(#"Document is not opened");
}
//End of debugging
//Fetch all the data from the entity
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"Users"];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
fetch.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *results = [document.managedObjectContext executeFetchRequest:fetch error:nil];
NSLog(#"Results on the database: %d", [results count]);
document is the same thing (at least I hope so, in most of the cases) as self.document; it's just an argument of the method where this code is located.
Here's the code for my .h and .m:
.h:
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#interface CoreDataViewController : UIViewController
#property (nonatomic, strong) UIManagedDocument *document;
#end
.m:
#import "CoreDataViewController.h"
#implementation CoreDataViewController
#synthesize document = _document;
- (void)fetchStuff:(UIManagedDocument *)document {
//Save data
NSEntityDescription *users = [NSEntityDescription insertNewObjectForEntityForName:#"Users" inManagedObjectContext:document.managedObjectContext];
[users setValue:#"Name Test" forKey:#"name"];
[users setValue:[NSNumber numberWithInt:20] forKey:#"age"];
[users setValue:#"Some Country" forKey:#"location"];
//Debugging
//no error ever shows up
NSError *error;
if(![document.managedObjectContext save:&error]) {
NSLog(#"Error: %#", error);
}
//this is just to show that the problem may not be with my UIManagedDocument (self.document), since the NSLog never gets called.
if(document.documentState != UIDocumentStateNormal) {
NSLog(#"Document is not opened");
}
//End of debugging
//Fetch all the data from the entity
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"Users"];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
fetch.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *results = [document.managedObjectContext executeFetchRequest:fetch error:nil];
NSLog(#"Results on the database: %d", [results count]);
}
- (void)useDocument {
if(![[NSFileManager defaultManager] fileExistsAtPath:[self.document.fileURL path]]) {
[self.document saveToURL:self.document.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){
if(success == YES) NSLog(#"created");
[self fetchStuff:self.document];
}];
} else if(self.document.documentState == UIDocumentStateClosed) {
[self.document openWithCompletionHandler:^(BOOL success) {
if(success == YES) NSLog(#"opened");
[self fetchStuff:self.document];
}];
} else if(self.document.documentState == UIDocumentStateNormal) {
[self fetchStuff:self.document];
}
}
- (void)setDocument:(UIManagedDocument *)document {
if(_document != document) {
_document = document;
[self useDocument];
}
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if(!self.document) {
NSURL *url = [[[NSFileManager defaultManager]URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"Database"];
self.document = [[UIManagedDocument alloc]initWithFileURL:url];
}
}
#end
Note: There's also my data model, which has an entity called "Users", with the attributes age, location, name.
The data was being saved sometimes because of the autosaving, which happens each X (maybe 10, I need to check on documentation) seconds. To force the save, I should've used this:
[documentation saveToURL:documentation.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
if(success == YES) NSLog(#"Awesome, it's saved!");
}];
Although it works fine adding this code to fetchStuff:, it'd be better to implement this when the user exits the screen, since it could be automatically saved via autosave.
You should not call save on the UIManagedDocument MOC. Here's an edited version of your code. Please try it.
//Save data
NSEntityDescription *users = [NSEntityDescription insertNewObjectForEntityForName:#"Users" inManagedObjectContext:document.managedObjectContext];
[users setValue:#"Name Test" forKey:#"name"];
[users setValue:[NSNumber numberWithInt:20] forKey:#"age"];
[users setValue:#"Some Country" forKey:#"location"];
// Removed save on UIMDMOC - We let auto-save do the work
// However, we have to tell the document that it needs to
// be saved. Now, the changes in the "main" MOC will get pushed
// to the "parent" MOC, and when appropriate, will get save to disk.
[document updateChangeCount:UIDocumentChangeDone];
if(self.document.documentState != UIDocumentStateNormal) {
NSLog(#"Document is not opened");
}
//Fetch all the data from the entity
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"Users"];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
fetch.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *results = [document.managedObjectContext executeFetchRequest:fetch error:nil];
NSLog(#"Results on the database: %d", [results count]);
OK... That was not too painful... just removed the call to save, and replaced it with a call that tells the UIManagedDocument that some changes have been made, and need to be saved.
Record *newentry = [NSEntityDescription insertNewObjectForEntityForName:#"Record" inManagedObjectContext:self.mManagedObjectContext];
newentry.code = entryStr;
NSError *error;
if ([self.mManagedObjectContext save:&error])
{
NSLog(#"save successfully");
}
else
{
NSLog(#"fail to save");
}