Passing a Value Asynchronously - objective-c

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.

Related

Can't Access Entity In Core Data From Another View Controller

I am a novice Objective-C programmer and core data user. This is my first time using core data. What I am trying to do is put entities in a core data database and then retrieve and edit them in another View Controller.
Here is the code in my ViewController.m:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsDirectory = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
NSString *documentName = #"myDocument";
NSURL *url = [documentsDirectory URLByAppendingPathComponent:documentName];
UIManagedDocument *document = [[UIManagedDocument alloc]initWithFileURL:url];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[url path] ];
if(fileExists)
{
[document openWithCompletionHandler:^(BOOL success){
if(!success) NSLog(#"Error (this isn't printed)");
}];
}
else
{
[document saveToURL:url forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){
if(!success) NSLog(#"Error (this isn't printed)");
}];
NSManagedObjectContext *context = document.managedObjectContext;
QuestionResponse *question1 = [NSEntityDescription insertNewObjectForEntityForName: #"QuestionResponse" inManagedObjectContext: context];
QuestionResponse *question2 = [NSEntityDescription insertNewObjectForEntityForName: #"QuestionResponse" inManagedObjectContext: context];
QuestionResponse *question3 = [NSEntityDescription insertNewObjectForEntityForName: #"QuestionResponse" inManagedObjectContext: context];
QuestionResponse *question4 = [NSEntityDescription insertNewObjectForEntityForName: #"QuestionResponse" inManagedObjectContext: context];
question1.question = #"What is your favorite food?";
question2.question = #"Who is your favorite fictional character?";
question3.question = #"Name someone who loves you:";
question4.question = #"What is your favorite place (real or fictional)?";
question1.order = [NSNumber numberWithInt:1];
question2.order = [NSNumber numberWithInt:2];
question3.order = [NSNumber numberWithInt:3];
question4.order = [NSNumber numberWithInt:4];
question1.group = [NSNumber numberWithInt:1];
question2.group = [NSNumber numberWithInt:1];
question3.group = [NSNumber numberWithInt:2];
question3.group = [NSNumber numberWithInt:2];
question1.display = [NSNumber numberWithBool:NO];
question2.display = [NSNumber numberWithBool:NO];
question3.display = [NSNumber numberWithBool:NO];
question4.display = [NSNumber numberWithBool:NO];
NSFetchRequest* request2 = [NSFetchRequest fetchRequestWithEntityName:# "QuestionResponse"];
NSError* error2 = nil;
NSArray* results = [context executeFetchRequest:request2 error:&error2];
if (!results || !results.count){
NSLog(#"No elements (this isn't printed)");
}
if(results.count == 4)
{
NSLog(#"We have four entities (this is what is printed)");
}
}
}
In my AnswerViewController.m this is the code:
- (IBAction)answerQuestion1:(id)sender {
//File creation begins here (same as in View Controller)
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsDirectory = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
NSString *documentName = #"myDocument";
NSURL *url = [documentsDirectory URLByAppendingPathComponent:documentName];
UIManagedDocument *document = [[UIManagedDocument alloc]initWithFileURL:url];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[url path] ];
if(fileExists)
{
[document openWithCompletionHandler:^(BOOL success){
if(!success) NSLog(#"Error");
}];
NSLog(#"File Exists (this is printed)");
}
else
{
[document saveToURL:url forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){
if(!success)NSLog(#"There has been a problem (this isn't printed)");
}];
}
NSManagedObjectContext *context = document.managedObjectContext;
//File Creation ends here
NSError *error = nil;
NSFetchRequest* request2 = [NSFetchRequest fetchRequestWithEntityName:# "QuestionResponse"];
NSError* error2 = nil;
NSArray* results = [context executeFetchRequest:request2 error:&error2];
if (!results || !results.count){
NSLog(#"No entities (this is what is printed");
}
else
{
NSLog(#"Some entities (this isn't printed)");
}
}
In my ViewController.m the core data has four elements but when I try to access them in AnswereViewController.m there is nothing in core data. Why is this happening and how can I access my elements from AnswereViewController.m?
In order to use CoreData successfully, you have to first understand the flow of it. In essence, CoreData can be broken down into two main parts:
the database (so the .sqlite file itself)
the persistent store coordinator (henceforth known as psc)
What you have done so far in ViewController.m is that you have told the compiler to insert your 4 objects into your managedObjectContext. This gets done on a psc level. The psc acts like a scratchpad for all of your database changes. Until you actually tell it to save it, it will only live in the psc and not in your .sqlite file. What you need to do then is to call:
[context save:&error2]
This will tell the compiler to push your current status of the psc into the database so it persists.
I hope this helps you.

How do I loop through tweets to access geo information and add to an array

How would I loop through the JSON returned by a TWRequest to get the geo information of a tweet? I am using the code below - I have marked up the bit I am unsure about. the text component works fine, I'm just not sure how to create the array of geo data and access this...
- (void)fetchTweets
{
AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
//NSLog(#"phrase carried over is %#", delegate.a);
// Do a simple search, using the Twitter API
TWRequest *request = [[TWRequest alloc] initWithURL:[NSURL URLWithString:
[NSString stringWithFormat:#"http://search.twitter.com/search.json?q=%#", delegate.a]]
parameters:nil requestMethod:TWRequestMethodGET];
// Notice this is a block, it is the handler to process the response
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
if ([urlResponse statusCode] == 200)
{
// The response from Twitter is in JSON format
// Move the response into a dictionary and print
NSError *error;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
//NSLog(#"Twitter response: %#", dict);
NSArray *results = [dict objectForKey:#"results"];
//Loop through the results
for (NSDictionary *tweet in results) {
// Get the tweet
NSString *twittext = [tweet objectForKey:#"text"];
//added this one - need to check id NSString is ok??
NSString *twitlocation = [tweet objectForKey:#"geo"];
// Save the tweet to the twitterText array
[_twitterText addObject:twittext];
//this is the loop for the location
[twitterLocation addObject:twitlocation];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
else
NSLog(#"Twitter error, HTTP response: %i", [urlResponse statusCode]);
}];
}
"geo" is deprecated and probably not filled at all. I far as I remember it was deprecated in Twitter API v1.0 too. Try this code:
- (void)fetchTweets
{
AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
//NSLog(#"phrase carried over is %#", delegate.a);
// Do a simple search, using the Twitter API
TWRequest *request = [[TWRequest alloc] initWithURL:[NSURL URLWithString:
[NSString stringWithFormat:#"http://search.twitter.com/search.json?q=%#", delegate.a]]
parameters:nil requestMethod:TWRequestMethodGET];
// Notice this is a block, it is the handler to process the response
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
if ([urlResponse statusCode] == 200)
{
// The response from Twitter is in JSON format
// Move the response into a dictionary and print
NSError *error;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
//NSLog(#"Twitter response: %#", dict);
NSArray *results = [dict objectForKey:#"results"];
//Loop through the results
for (NSDictionary *tweet in results) {
// Get the tweet
NSString *twittext = [tweet objectForKey:#"text"];
//added this one - need to check id NSString is ok??
id jsonResult = [tweet valueForKeyPath:#"coordinates.coordinates"];
if ([NSNull null] != jsonResult) {
if (2 == [jsonResult count]) {
NSDecimalNumber* longitude = [jsonResult objectAtIndex:0];
NSDecimalNumber* latitude = [jsonResult objectAtIndex:1];
if (longitude && latitude) {
// here you have your coordinates do whatever you like
[twitterLocation addObject:[NSString stringWithFormat:#"%#,%#", latitude, longitude]];
}
else {
NSLog(#"Warning: bad coordinates: %#", jsonResult);
}
}
else {
NSLog(#"Warning: bad coordinates: %#", jsonResult);
}
}
// Save the tweet to the twitterText array
[_twitterText addObject:twittext];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
else
NSLog(#"Twitter error, HTTP response: %i", [urlResponse statusCode]);
}];
}

AFNetworking HTTPRequestOperation need to set array from completion block but this isn't working?

I'm using AFNetworking with AFHTTPRequestOperation to pull XML data from a webservice. This is working fine and im getting the data I need but I need to split this data into objects and initialize a NSMutableArray with this data. This is working in the completion block, but just before I return the array in my method the data is gone? How do I do this?
Here is some of my code:
NSMutableArray *result = [[NSMutableArray alloc] init];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[httpClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString* response = [operation responseString];
NSData* xmlData = [response dataUsingEncoding:NSUTF8StringEncoding];
NSError *xmlError;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&xmlError];
NSArray *allElements = [doc.rootElement elementsForName:#"Misc"];
for (GDataXMLElement *current in allElements)
{
NSString *titel;
NSString *tekst;
NSArray *titels = [current elementsForName:#"Titel"];
if(titels.count > 0)
{
GDataXMLElement *firstTitel = (GDataXMLElement *) [titels objectAtIndex:0];
titel = firstTitel.stringValue;
} else continue;
NSArray *teksts = [current elementsForName:#"Tekst"];
if(teksts.count > 0)
{
GDataXMLElement *firstTekst = (GDataXMLElement *) [teksts objectAtIndex:0];
tekst = firstTekst.stringValue;
} else continue;
HVMGUniversalItem *item = [[HVMGUniversalItem alloc] initWithTitel:titel AndTekst:tekst];
[result addObject:item];
}
NSLog(#"%i", result.count);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", [operation error]);
}];
[operation start];
NSLog(#"%i", result.count);
return result;
What am I doing wrong? Why isn't the data present in the array when returning?
Why isn't the data present in the array when returning?
Because AFNetworking use an async pattern. So the return code will be performed before the operation will be completed.
You need to use a different approach or follow Can AFNetworking return data synchronously (inside a block)?. The latter is discouraged.
A solution could be to:
-> Create a NSOperationQueue within your class that will include your operation. Create it as a property for your class like.
#property (nonatomic, strong, readonly) NSOperationQueue* downloadQueue;
- (NSOperationQueue*)downloadQueue
{
if(downloadQueue) return downloadQueue;
downloadQueue = // alloc init here
}
-> Create a property for your array (synthesize also it)
#property (nonatomic, strong) NSMutableArray* result;
-> Wrap your code within a specific method like doOperation.
self.result = [[NSMutableArray alloc] init];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[httpClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
__weak YourClass* selfBlock = self;
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString* response = [operation responseString];
NSData* xmlData = [response dataUsingEncoding:NSUTF8StringEncoding];
NSError *xmlError;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&xmlError];
NSArray *allElements = [doc.rootElement elementsForName:#"Misc"];
for (GDataXMLElement *current in allElements)
{
NSString *titel;
NSString *tekst;
NSArray *titels = [current elementsForName:#"Titel"];
if(titels.count > 0)
{
GDataXMLElement *firstTitel = (GDataXMLElement *) [titels objectAtIndex:0];
titel = firstTitel.stringValue;
} else continue;
NSArray *teksts = [current elementsForName:#"Tekst"];
if(teksts.count > 0)
{
GDataXMLElement *firstTekst = (GDataXMLElement *) [teksts objectAtIndex:0];
tekst = firstTekst.stringValue;
} else continue;
HVMGUniversalItem *item = [[HVMGUniversalItem alloc] initWithTitel:titel AndTekst:tekst];
[selfBlock.result addObject:item];
}
NSLog(#"%i", result.count);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", [operation error]);
}];
[downloadQueue addOperation:operation];
-> if you need to notify that result has object send a notification, use the delegate pattern, etc...
Hope that helps.

how to return result after OpenWithCompletionHandler: is complete

Want to query a photo in the Coredata database
this is my code
this is the NSObjectSubclass category
//Photo+creak.h
#import "Photo+creat.h"
#implementation Photo (creat)
+(Photo *)creatPhotoByString:(NSString *)photoName inManagedObjectContext:(NSManagedObjectContext *)context{
Photo *picture = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Photo"];
request.predicate = [NSPredicate predicateWithFormat:#"name = %#", photoName];
NSArray *matches = [context executeFetchRequest:request error:nil];
if (!matches || [matches count]>1) {
//error
} else if ([matches count] == 0) {
picture = [NSEntityDescription insertNewObjectForEntityForName:#"Photo" inManagedObjectContext:context];
picture.name = photoName;
} else {
picture = [matches lastObject];
}
return picture;
}
+ (BOOL)isPhoto:(NSString *)photoName here:(NSManagedObjectContext *)context{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Photo"];
request.predicate = [NSPredicate predicateWithFormat:#"name = %#", photoName];
NSArray *matches = [context executeFetchRequest:request error:nil];
switch ([matches count]) {
case 1:
return YES;
break;
default:
return NO;
break;
}
}
#end
code inside of view controller
//View Controller
- (IBAction)insertData:(UIButton *)sender {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"test"];
UIManagedDocument *defaultDocument = [[UIManagedDocument alloc] initWithFileURL:url];
if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
[defaultDocument saveToURL:defaultDocument.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:NULL];
}
[defaultDocument openWithCompletionHandler:^(BOOL success) {
[Photo creatPhotoByString:#"test" inManagedObjectContext:defaultDocument.managedObjectContext];
[defaultDocument saveToURL:defaultDocument.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL];
}];
[sender setTitle:#"Okay" forState:UIControlStateNormal];
[sender setEnabled:NO];
}
- (IBAction)queryFromDatabase:(UIButton *)sender {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"test"];
UIManagedDocument *defaultDocument = [[UIManagedDocument alloc] initWithFileURL:url];
BOOL isItWorking = [checkPhoto isPhoto:#"test" inManagedDocument:defaultDocument];
if (isItWorking) {
[sender setTitle:#"Okay" forState:UIControlStateNormal];
} else {
[sender setTitle:#"NO" forState:UIControlStateNormal];
}
}
The NSObject Class that hook them up.
// checkPhoto.m
#import "checkPhoto.h"
#implementation checkPhoto
+ (BOOL)isPhoto:(NSString *)photoToCheck inManagedDocument:(UIManagedDocument *)document{
__block BOOL isPhotoHere = NO;
if (document.documentState == UIDocumentStateClosed) {
[document openWithCompletionHandler:^(BOOL success) {
isPhotoHere = [Photo isPhoto:photoToCheck here:document.managedObjectContext];
}];
}
return isPhotoHere;
}
#end
The coredata only have on Entity named "Photo", and it got only one attribute "name".
The problem is that the return always get execute before the block is complete and always return NO.
Test code here
Or should I do something else than openWithCompletionHandler when querying?
You need to rework your method to work asynchronously, like -openWithCompletionHandler:. It needs to take a block which is invoked when the answer is known and which receives the answer, true or false, as a parameter.
Then, the caller should pass in a block that does whatever is supposed to happen after the answer is known.
Or, alternatively, you should delay the whole chunk of logic which cares about the photo being in the database. It should be done after the open has completed.
You'd have to show more code for a more specific suggestion.
So, you could rework the isPhoto... method to something like:
+ (BOOL)checkIfPhoto:(NSString *)photoToCheck isInManagedDocument:(UIManagedDocument *)document handler:(void (^)(BOOL isHere))handler {
if (document.documentState == UIDocumentStateClosed) {
[document openWithCompletionHandler:^(BOOL success) {
handler([Photo isPhoto:photoToCheck here:document.managedObjectContext]);
}];
}
else
handler(NO);
}
Then you can rework this:
- (IBAction)queryFromDatabase:(UIButton *)sender {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"test"];
UIManagedDocument *defaultDocument = [[UIManagedDocument alloc] initWithFileURL:url];
[checkPhoto checkIfPhoto:#"test" isInManagedDocument:defaultDocument handler:^(BOOL isHere){
if (isHere) {
[sender setTitle:#"Okay" forState:UIControlStateNormal];
} else {
[sender setTitle:#"NO" forState:UIControlStateNormal];
}
}];
}
Try that
+(BOOL)isPhoto:(Photo *)photo inDataBase:(UIManagedDocument *)defaultDocument{
__block BOOL isPhotoThere = NO;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[defaultDocument openWithCompletionHandler:^(BOOL success) {
[defaultDocument.managedObjectContext performBlock:^{
isPhotoThere = [Photo checkPhoto:photo];
dispatch_semaphore_signal(sema);
}];
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
return isPhotoThere;
}

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");
}