How do I download Healthkit step and distance data? - iphone

I would like to download the step and distance data collected by the motion processor in the IPhone 5S (and later), and available in Apple's HealthKit, for analysis.
What's the easiest/best way to do this?
And clarifying (after new answers): is there any way to do it without writing a new iOS app? Are there any existing apps that provide the data, and/or any iCloud API that provides access.

I'm not sure it can help you but this is how I get steps
+ (void)readUsersStepFromHK:(NSDate*)startDate end:(NSDate*)endDate
{
stepBegin=startDate;
stepEnd=endDate;
if ([HKHealthStore isHealthDataAvailable])
{
HKUnit *unit = [HKUnit countUnit];
HKQuantityType *stepCountType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
[self fetchMostRecentDataOfQuantityType:stepCountType withCompletion:^(HKQuantity *mostRecentQuantity, NSError *error) {
if (!mostRecentQuantity)
{
//Either an error
}
else
{
double temCout=[mostRecentQuantity doubleValueForUnit:unit];
coutStep=temCout;
}
}];
}
}
+ (void)fetchMostRecentDataOfQuantityType:(HKQuantityType *)quantityType withCompletion:(void (^)(HKQuantity *mostRecentQuantity, NSError *error))completion {
NSSortDescriptor *timeSortDescriptor = [[NSSortDescriptor alloc] initWithKey:HKSampleSortIdentifierEndDate ascending:NO];
//=======
NSDate *startDate, *endDate; // Whatever you need in your case
startDate=stepBegin;
endDate=stepEnd;
// Your interval: sum by hour
NSDateComponents *intervalComponents = [[NSDateComponents alloc] init];
intervalComponents.hour = 1;
// Example predicate
NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionStrictStartDate];
// Since we are interested in retrieving the user's latest sample, we sort the samples in descending order, and set the limit to 1. We are not filtering the data, and so the predicate is set to nil.
HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:quantityType predicate:predicate limit:100 sortDescriptors:#[timeSortDescriptor] resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
if (!results) {
if (completion) {
completion(nil, error);
}
return;
}
if (completion) {
// If quantity isn't in the database, return nil in the completion block.
HKQuantitySample *quantitySample = results.firstObject;
HKQuantity *quantity = quantitySample.quantity;
completion(quantity, error);
}
}];
[healthStore executeQuery:query];
}
hop this help !

if (NSClassFromString(#"HKHealthStore") && [HKHealthStore isHealthDataAvailable])
{
// Add your HealthKit code here
HKHealthStore *healthStore = [[HKHealthStore alloc] init];
// Share body mass, height and body mass index etc....
NSSet *shareObjectTypes = [NSSet setWithObjects:
[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass],
[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeight],
[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning],
[HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount],
nil];
// Read date of birth, biological sex and step count etc
NSSet *readObjectTypes = [NSSet setWithObjects:
[HKObjectType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierDateOfBirth],
[HKObjectType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierBiologicalSex],
[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning],
[HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount],
nil];
HKQuantityType *type = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
// Request access
[healthStore requestAuthorizationToShareTypes:shareObjectTypes
readTypes:readObjectTypes
completion:^(BOOL success, NSError *error) {
if(success == YES)
{
//[healthStore ];
//NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionStrictStartDate];
// NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:HKSampleSortIdentifierStartDate ascending:YES];
NSSortDescriptor *timeSortDescription = [[NSSortDescriptor alloc] initWithKey:HKSampleSortIdentifierEndDate ascending:NO];
HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:type
predicate:nil
limit:HKObjectQueryNoLimit
sortDescriptors:#[timeSortDescription]
resultsHandler:^(HKSampleQuery *query, NSArray *result, NSError *error){
NSLog(#"RESULT : => %#",result);
if(!error && result)
{ long totalSteps=0;
for(HKQuantitySample *quantitySample in result)
{
// your code here
HKQuantity *quantity=quantitySample.quantity;
//HKQuantity *quantity = quantitySample.quantity;
NSString *string=[NSString stringWithFormat:#"%#",quantity];
NSString *newString1 = [string stringByReplacingOccurrencesOfString:#" count" withString:#""];
NSInteger count=[newString1 integerValue];
totalSteps=totalSteps+count;
}
//using total steps
}
}];
[healthStore executeQuery:query];
}
else
{
// Determine if it was an error or if the
// user just canceld the authorization request
//Fit_AAPLprofileviewcontroller_m.html
}
}];
}

You can perform a simple query for steps (and any other samples stored in HealthKit) using a HKSampleQuery. If you would like HealthKit to aggregate the samples for you, you could use a HKStatisticsQuery or HKStatisticsCollectionQuery instead. Before querying for the user's HealthKit data, you will need to ask for permission to access it with -[HKHealthStore
requestAuthorizationToShareTypes:readTypes:completion:
].
For a general introduction to writing applications that integrate with HealthKit, I recommend that you watch the WWDC talk.

Related

Predicate and expression to fetch complex request core data

I have to make a complex core data fetch request but I don't know if it can be made.
This is my scenario: just one entity (Expense) with these attributes:
Cost (NSDecimalNumber)
Deposit (NSDecimalNumber)
Category (NSString)
Paid (Boolean Value)
The request should return the 3 most expensive categories but these are the rules that must be respected:
If Paid == YES, Expense cost should be added to Expense category total
If Paid == NO && Deposit > 0, Expense deposit should be added to Expense category total
If Paid == NO, nothing should be added to Expense category total
Using NSExpression, I'm able to calculate every total per category but it also includes cost of Expenses not paid.
Is there a way to accomplish this?
Thank you so much!
You could, for example, use a NSFetchRequest:
// Build the fetch request
NSString *entityName = NSStringFromClass([Expense class]);
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = entity;
which filters only relevant expenses:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(paid == YES) OR ((paid == NO) AND (deposit > 0))"];
request.predicate = predicate;
and sums up the cost and depost attributes:
NSExpressionDescription *(^makeExpressionDescription)(NSString *, NSString *) = ^(NSString *keyPath, NSString *name)
{
// Create an expression for the key path.
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:keyPath];
// Create an expression to represent the function you want to apply
NSExpression *totalExpression = [NSExpression expressionForFunction: #"sum:" arguments: #[keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
// The name is the key that will be used in the dictionary for the return value
expressionDescription.name = name;
expressionDescription.expression = totalExpression;
expressionDescription.expressionResultType = NSDecimalAttributeType;
return expressionDescription;
};
NSExpressionDescription *totalCostDescription = makeExpressionDescription(#"cost", #"totalCost");
NSExpressionDescription *totalDepositDescription = makeExpressionDescription(#"deposit", #"totalDeposit");
// Specify that the request should return dictionaries.
request.resultType = NSDictionaryResultType;
request.propertiesToFetch = #[categoryDescription,
paidDescription,
totalCostDescription,
totalDepositDescription];
and group the results by category and paid status:
// Get 'category' and 'paid' attribute descriptions
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName
inManagedObjectContext:context];
NSDictionary *attributes = [entity attributesByName];
NSAttributeDescription *categoryDescription = attributes[#"category"];
NSAttributeDescription *paidDescription = attributes[#"paid"];
// Group by 'category' and 'paid' attributes
request.propertiesToGroupBy = #[categoryDescription, paidDescription];
You'll get paid and unpaid expenses summed up
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
all you need to do is combine (and sort) then:
if (results) {
NSMutableDictionary *combined = [NSMutableDictionary dictionary];
for (NSDictionary *result in results) {
NSString *category = result[#"category"];
BOOL paid = [result[#"paid"] boolValue];
NSDecimalNumber *total = result[paid ? #"totalCost" : #"totalDeposit"];
NSDecimalNumber *sum = combined[category];
if (sum) {
total = [total decimalNumberByAdding:sum];
}
combined[category] = total;
}
NSArray *sortedCategories = [combined keysSortedByValueUsingSelector:#selector(compare:)];
[sortedCategories enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSLog(#"Category %#: %#", obj, combined[obj]);
}];
}
else {
NSLog(#"Error: %#", error);
}

Using a file (CSV) instead of using CoreData

An app I'm building contains a catalogue of thousands of items, which need to be stored on the phone. Currently I am achieving this through CoreData, as logically it seemed like the best place to put it. I'm using GCD to run the CoreData insertion processes in the background and showing a progress bar / current percentage complete. This works as expected, however for only 5000 items, it's taking 8 minutes to complete on an iPhone 4. This application will be used on the 3GS and up, and will more likely contain 30/40 thousand items once it launches. Therefore this processing time is going to be horrifically long.
Is there any way I can use a CSV file or something to search through instead of storing each item in CoreData? I'm assuming there are some efficiency downfalls with an approach like this, but it would alleviate the excessive wait times. Unless there is another solution that would help with this problem.
Thanks.
EDIT:
I'm not sure how I'd go about saving the context at the end of the entire operation, as it uses a separate context within the loop. Any suggestions for this would be very much appreciated. I've got no idea how to progress with this.
Insertion Code Being Used
- (void) processUpdatesGCD {
NSArray *jsonArray=[NSJSONSerialization JSONObjectWithData:_responseData options:0 error:nil];
NSArray *products = [jsonArray valueForKey:#"products"];
NSArray *deletions;
if ([jsonArray valueForKey:#"deletions"] == (id)[NSNull null]){
self.totalCount = [products count];
} else {
deletions = [jsonArray valueForKey:#"deletions"];
self.totalCount = [products count] + [deletions count];
}
self.productDBCount = 0;
_delegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = _delegate.managedObjectContext;
self.persistentStoreCoordinator = [managedObjectContext persistentStoreCoordinator];
_managedObjectContext = managedObjectContext;
// Create a new background queue for GCD
dispatch_queue_t backgroundDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
for (id p in products) {
// id product = p;
// Dispatch the following code on our background queue
dispatch_async(backgroundDispatchQueue,
^{
id product = p;
// Because at this point we are running in another thread we need to create a
// new NSManagedContext using the app's persistance store coordinator
NSManagedObjectContext *backgroundThreadContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundThreadContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
NSFetchRequest *BGRequest = [[NSFetchRequest alloc] init];
NSLog(#"Running.. (%#)", product);
[BGRequest setEntity:[NSEntityDescription entityForName:#"Products" inManagedObjectContext:backgroundThreadContext]];
[BGRequest setIncludesSubentities:NO];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"codes == %#", [product valueForKey:#"product_codes"]];
[BGRequest setPredicate:predicate];
NSError *err;
NSArray *results = [backgroundThreadContext executeFetchRequest:BGRequest error:&err];
if (results.count == 0){
// Product doesn't exist with code, make a new product
NSLog(#"Product not found for add/update (%#)", [product valueForKey:#"product_name"]);
NSManagedObject* newProduct;
newProduct = [NSEntityDescription insertNewObjectForEntityForName:#"Products" inManagedObjectContext:backgroundThreadContext];
[newProduct setValue:[product valueForKey:#"product_name"] forKey:#"name"];
[newProduct setValue:[product valueForKey:#"product_codes"] forKey:#"codes"];
if ([product valueForKey:#"information"] == (id)[NSNull null]){
// No information, NULL
[newProduct setValue:#"" forKey:#"information"];
} else {
NSString *information = [product valueForKey:#"information"];
[newProduct setValue:information forKey:#"information"];
}
} else {
NSLog(#"Product found for add/update (%#)", [product valueForKey:#"product_name"]);
// Product exists, update existing product
for (NSManagedObject *r in results) {
[r setValue:[product valueForKey:#"product_name"] forKey:#"name"];
if ([product valueForKey:#"information"] == (id)[NSNull null]){
// No information, NULL
[r setValue:#"" forKey:#"information"];
} else {
NSString *information = [product valueForKey:#"information"];
[r setValue:information forKey:#"information"];
}
}
}
// Is very important that you save the context before moving to the Main Thread,
// because we need that the new object is writted on the database before continuing
NSError *error;
if(![backgroundThreadContext save:&error])
{
NSLog(#"There was a problem saving the context (add/update). With error: %#, and user info: %#",
[error localizedDescription],
[error userInfo]);
}
// Now let's move to the main thread
dispatch_async(dispatch_get_main_queue(), ^
{
// If you have a main thread context you can use it, this time i will create a
// new one
// NSManagedObjectContext *mainThreadContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
// [mainThreadContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
self.productDBCount = self.productDBCount + 1;
float progress = ((float)self.productDBCount / (float)self.totalCount);
int percent = progress * 100.0f;
// NSNumber *progress = [NSNumber numberWithFloat:((float)self.productDBCount / (float)self.totalCount)];
self.downloadUpdateProgress.progress = progress;
self.percentageComplete.text = [NSString stringWithFormat:#"%i", percent];
NSLog(#"Added / updated product %f // ProductDBCount: %i // Percentage progress: %i // Total Count: %i", progress, self.productDBCount, percent, self.totalCount);
if (self.productDBCount == self.totalCount){
[self updatesCompleted:[jsonArray valueForKey:#"last_updated"]];
}
});
});
}
if ([deletions count] > 0){
for (id d in deletions){
dispatch_async(backgroundDispatchQueue,
^{
id deleted = d;
// Because at this point we are running in another thread we need to create a
// new NSManagedContext using the app's persistance store coordinator
NSManagedObjectContext *backgroundThreadContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundThreadContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
NSFetchRequest *BGRequest = [[NSFetchRequest alloc] init];
// NSLog(#"Running.. (%#)", deleted);
[BGRequest setEntity:[NSEntityDescription entityForName:#"Products" inManagedObjectContext:backgroundThreadContext]];
[BGRequest setIncludesSubentities:NO];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"codes == %#", [deleted valueForKey:#"product_codes"]];
[BGRequest setPredicate:predicate];
NSError *err;
NSArray *results = [backgroundThreadContext executeFetchRequest:BGRequest error:&err];
if (results.count == 0){
// Product doesn't exist with code, make a new product
NSLog(#"Product not found, can't delete.. %#", [deleted valueForKey:#"product_name"]);
} else {
NSLog(#"Product found, deleting: %#", [deleted valueForKey:#"product_name"]);
// Product exists, update existing product
for (NSManagedObject *r in results) {
[backgroundThreadContext deleteObject:r];
}
}
// Is very important that you save the context before moving to the Main Thread,
// because we need that the new object is writted on the database before continuing
NSError *error;
if(![backgroundThreadContext save:&error])
{
NSLog(#"There was a problem saving the context (delete). With error: %#, and user info: %#",
[error localizedDescription],
[error userInfo]);
}
// Now let's move to the main thread
dispatch_async(dispatch_get_main_queue(), ^
{
self.productDBCount = self.productDBCount + 1;
float progress = ((float)self.productDBCount / (float)self.totalCount);
int percent = progress * 100.0f;
// NSNumber *progress = [NSNumber numberWithFloat:((float)self.productDBCount / (float)self.totalCount)];
self.downloadUpdateProgress.progress = progress;
self.percentageComplete.text = [NSString stringWithFormat:#"%i", percent];
NSLog(#"Deleted product %f // ProductDBCount: %i // Percentage progress: %i // Total Count: %i", progress, self.productDBCount, percent, self.totalCount);
if (self.productDBCount == self.totalCount){
[self updatesCompleted:[jsonArray valueForKey:#"last_updated"]];
}
/*
*
* Change the completion changes to a method. Check to see if the total number of products == total count. If it does, run the completion method.
*
*/
});
});
}
}
}
Put the IF inside the dispatch, run one save at the end
// Create a new background queue for GCD
dispatch_queue_t backgroundDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
// id product = p;
// Dispatch the following code on our background queue
dispatch_async(backgroundDispatchQueue,
^{
NSManagedObjectContext *backgroundThreadContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundThreadContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
for (id p in products) {
id product = p;
// Because at this point we are running in another thread we need to create a
// new NSManagedContext using the app's persistance store coordinator
NSFetchRequest *BGRequest = [[NSFetchRequest alloc] init];
NSLog(#"Running.. (%#)", product);
[BGRequest setEntity:[NSEntityDescription entityForName:#"Products" inManagedObjectContext:backgroundThreadContext]];
[BGRequest setIncludesSubentities:NO];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"codes == %#", [product valueForKey:#"product_codes"]];
[BGRequest setPredicate:predicate];
NSError *err;
NSArray *results = [backgroundThreadContext executeFetchRequest:BGRequest error:&err];
if (results.count == 0){
// Product doesn't exist with code, make a new product
NSLog(#"Product not found for add/update (%#)", [product valueForKey:#"product_name"]);
NSManagedObject* newProduct;
newProduct = [NSEntityDescription insertNewObjectForEntityForName:#"Products" inManagedObjectContext:backgroundThreadContext];
[newProduct setValue:[product valueForKey:#"product_name"] forKey:#"name"];
[newProduct setValue:[product valueForKey:#"product_codes"] forKey:#"codes"];
if ([product valueForKey:#"information"] == (id)[NSNull null]){
// No information, NULL
[newProduct setValue:#"" forKey:#"information"];
} else {
NSString *information = [product valueForKey:#"information"];
[newProduct setValue:information forKey:#"information"];
}
} else {
NSLog(#"Product found for add/update (%#)", [product valueForKey:#"product_name"]);
// Product exists, update existing product
for (NSManagedObject *r in results) {
[r setValue:[product valueForKey:#"product_name"] forKey:#"name"];
if ([product valueForKey:#"information"] == (id)[NSNull null]){
// No information, NULL
[r setValue:#"" forKey:#"information"];
} else {
NSString *information = [product valueForKey:#"information"];
[r setValue:information forKey:#"information"];
}
}
}
// Is very important that you save the context before moving to the Main Thread,
// because we need that the new object is writted on the database before continuing
// Now let's move to the main thread
dispatch_async(dispatch_get_main_queue(), ^
{
// If you have a main thread context you can use it, this time i will create a
// new one
// NSManagedObjectContext *mainThreadContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
// [mainThreadContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
self.productDBCount = self.productDBCount + 1;
float progress = ((float)self.productDBCount / (float)self.totalCount);
int percent = progress * 100.0f;
// NSNumber *progress = [NSNumber numberWithFloat:((float)self.productDBCount / (float)self.totalCount)];
self.downloadUpdateProgress.progress = progress;
self.percentageComplete.text = [NSString stringWithFormat:#"%i", percent];
NSLog(#"Added / updated product %f // ProductDBCount: %i // Percentage progress: %i // Total Count: %i", progress, self.productDBCount, percent, self.totalCount);
NSDate *currentProcessedDate = [NSDate date];
NSTimeInterval timeSinceStarted = [currentProcessedDate timeIntervalSinceDate:self.startProcessing];
NSInteger remainingProcesses = self.totalCount - self.productDBCount;
float timePerProcess = timeSinceStarted / (float)self.productDBCount;
float remainingTime = timePerProcess * (float)remainingProcesses;
self.timeRemaining.text = [NSString stringWithFormat:#"ETA: %0.0f minutes", fmodf(remainingTime, 60.0f)];
if (self.productDBCount == self.totalCount){
[self updatesCompleted:[jsonArray valueForKey:#"last_updated"]];
}
/*
*
* Change the completion changes to a method. Check to see if the total number of products == total count. If it does, run the completion method.
*
*/
});
}
NSError *error;
if(![backgroundThreadContext save:&error])
{
NSLog(#"There was a problem saving the context (add/update). With error: %#, and user info: %#",
[error localizedDescription],
[error userInfo]);
}
});
Ok, here's your problem.
Every time you insert a record, you do a save operation to the context.
Now, don't do it, that's what takes alot of time.
Do the save operation once, in the end of the loop, not every time you insert a record.
In your case I would check what is really time consuming?
Is it downloading data, is it importing data to CoreData?
Where do you get data from? Do you download it or you have it in Application Bundle?
CoreData is faster then CSV file. So it wont make your app faster.
Some tricks:
While importing data just save context at the end of the process. Do not save context in a loop.
If you do not need to download data and can put in the bundle, you can create coredata file in the simulator, put in the bundle and copy the file on first launch. It is really much more faster then importing data.

iOS+ Parse.com : kPFCachePolicyCacheThenNetwork, retrieve data two times

I have build an iOS app using Parse.com
I want to retrieve data from cache initially & then from network
For this i have used kPFCachePolicyCacheThenNetwork cache policy wchich is opt for my requirement.
PFQuery *employesquery = [PFQuery queryWithClassName:#"employesTable"];
[employesquery whereKey:#"UserID" equalTo:[PFUser currentUser]];
[employesquery includeKey:#"empID"];
[employesquery includeKey:#"empID.user"];
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
//APPLIES CACHE... ********
employesquery.cachePolicy = kPFCachePolicyCacheThenNetwork;
[employesquery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
[hud hide:YES];
NSMutableArray *empData = [[NSMutableArray alloc] init];
for (PFObject *object in objects)
{
//Getting Data For Item
PFObject *employeeObject = [object objectForKey:#"empID"];
[empData addObject:employeeObject];
}
[self fetchEmployeeData:empData];
}];
But using this Each data retrieve two times, repeatable data.
How to avoid this repeatable data,
Once the data is getting from networks the previously shown data (using cache) gets cleared/ hidden.
I have tried with [PFQuery clearAllCachedResults];
It was cleared all the cache so that there no data in cache for the next iteration.
When using kPFCachePolicyCacheThenNetwork as your caching policy your callback is always triggered twice. This is because the policy is to use the cache and then go to the network, not to use the cache exclusively.
The solution is to keep track of how many times your block has been called, so you can establish whether you're getting the cached result or the fresh result from the network. Parse have more information about this here.
You must clear the data array which is used for store objects queried from parse in the block findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error).
- (void)refreshData
{
PFUser *currentUser = [PFUser currentUser];
NSPredicate *predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"%# = '%#' OR %# = '%#'",
kMessageUserSendIdKey, currentUser.objectId,
kMessageUserReceiveIdKey, currentUser.objectId]];
PFQuery *query = [PFQuery queryWithClassName:kMessageClassKey predicate:predicate];
[query addDescendingOrder:kMessageTimeCreatedKey];
[query includeKey:kMessageUserSendKey];
[query includeKey:kMessageUserReceiveKey];
[query setCachePolicy:kPFCachePolicyCacheThenNetwork];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// TODO: Clear the data array
self.messagesDataArray = [NSMutableArray new];
if (objects.count > 0) {
[self.messagesDataArray addObject:objects[0]];
for (int i = 1; i < objects.count; i++) {
PFObject *messageChat = [objects objectAtIndex:i];
if (![self isExistWithMessageChat:messageChat]){
[self.messagesDataArray addObject:messageChat];
}
}
[self.tableView reloadData];
}
}
else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}

Optimizing this Core Data request

I have an entity in Core Data named MusicInterest. I have to add 5000 or so of these at a time and my current process is to query to see if the MusicInterest exists already, if not create a new one.
It seems this requires 5000 trips to the store to see if each title exists. There are also, of course, insert trips, but the 5000 queries is what's slowing me down.
Each FacebookFriend will have multiple music interests, and I enumerate through each one using an array of string titles, calling the following code.
Any ideas how to optimize this?
+ (MusicInterest*) musicInterestForFacebookFriend:(FacebookFriend*)facebookFriend WithTitle:(NSString*)musicTitle UsingManagedObjectContext:(NSManagedObjectContext*)moc
{
// query to see if there
NSArray *matches = [self queryForMusicTitle:musicTitle moc:moc];
if (([matches count] >= 1)) {
// NSLog(#"Music already in database");
MusicInterest *existingMusic = [matches lastObject];
[existingMusic addLikedByObject:facebookFriend];
return [matches lastObject];
} else {
// create new Music Interest
MusicInterest *newMusic = [NSEntityDescription insertNewObjectForEntityForName:#"MusicInterest" inManagedObjectContext:moc];
newMusic.title = musicTitle;
[newMusic addLikedByObject:facebookFriend];
return newMusic;
}
}
+ (NSArray *)queryForMusicTitle:(NSString *)MusicTitle moc:(NSManagedObjectContext *)moc
{
// query to see if there
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"MusicInterest"];
request.predicate = [NSPredicate predicateWithFormat:#"title == %#", [NSString stringWithFormat:#"%#", MusicTitle]];
NSError *error = nil;
NSArray *matches = [moc executeFetchRequest:request error:&error];
if (error) {
NSLog(#"Error querying title in Music interest. Error = %#", error);
}
return matches;
}
UPDATE:
I employed the design suggested in the Core Data programming guide and it reduced my time from 12 seconds to 4 seconds (still needs some optimization in other areas :)
The guide only includes half the sample code - I thought I would share my complete implementation:
musicArray = [[music componentsSeparatedByString:#", "] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
if (obj1 > obj2)
return NSOrderedDescending;
else if (obj1 < obj2)
return NSOrderedAscending;
return NSOrderedSame;
}];
if (musicArray) {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"MusicInterest"];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"title IN %#", musicArray]];
[fetchRequest setSortDescriptors:
#[[[NSSortDescriptor alloc] initWithKey: #"title" ascending:YES]]];
NSError *fetchError = nil;
NSArray *musicInterestMatchingTitles = [backgroundContext executeFetchRequest:fetchRequest error:&fetchError];
if ([musicArray count] > 0) {
// walk musicArray and musicInterestsMatchingTitles in parallel
for (int i = 0; i < [musicArray count]; i++) {
NSString *title = musicArray[i];
if (i < [musicInterestMatchingTitles count]) {
MusicInterest *comparingMusicInterest = musicInterestMatchingTitles[i];
// compare each title
if (![title isEqualToString:comparingMusicInterest.title]) {
// if it doesn't exist as a ManagedObject (a MusicInterest), create one
MusicInterest *musicInterest = [MusicInterest createNewMusicInterestUsingManagedObjectContext:backgroundContext];
musicInterest.title = title;
[musicInterest addLikedByObject:friend];
} else {
// otherwise, just establish the relationship
[comparingMusicInterest addLikedByObject:friend];
}
} else {
// if there are no existing matching managedObjects, create one
MusicInterest *musicInterest = [MusicInterest createNewMusicInterestUsingManagedObjectContext:backgroundContext];
musicInterest.title = title;
[musicInterest addLikedByObject:friend];
}
}
}
}
}];
[self saveBackgroundContext:backgroundContext];
Implementing Find-or-Create Efficiently in the "Core Data Programming Guide" describes a pattern that might be useful here. The basic idea is:
Sort your list of items that you want to insert/update by some unique id that is also stored in
the database.
Perform a single fetch request that fetches all objects from the database that have an id from your list, sorted by the same id.
Now traverse your list and the array of fetched items in parallel, to find which items have to be inserted and which items already exist and can be updated.

Error: NSArray was mutated while being enumerated thrown when accessing globals and Core Data

I have this piece of code which I am using to update some values in Core Data when another object is added:
//Create new receipt
Receipt *receipt = [[Receipt alloc] init];
receipt.project = self.projectLabel.text;
receipt.amount = self.amountTextField.text;
receipt.descriptionNote = self.descriptionTextField.text;
receipt.business = self.businessNameTextField.text;
receipt.date = self.dateLabel.text;
receipt.category = self.categoryLabel.text;
receipt.paidBy = self.paidByLabel.text;
receipt.receiptImage1 = self.receiptImage1;
//Need to set this to 2
receipt.receiptImage2 = self.receiptImage1;
receipt.receiptNumber = #"99";
int count = 0;
int catCount = 0;
for (Project *p in appDelegate.projects)
{
if ([p.projectName isEqualToString:receipt.project]){
double tempValue = [p.totalValue doubleValue];
tempValue += [receipt.amount doubleValue];
NSString *newTotalValue = [NSString stringWithFormat:#"%.02f", tempValue];
NSString *newProjectName = p.projectName;
//remove entity from Core Data
NSFetchRequest * allProjects = [[NSFetchRequest alloc] init];
[allProjects setEntity:[NSEntityDescription entityForName:#"Project" inManagedObjectContext:appDelegate.managedObjectContext]];
[allProjects setIncludesPropertyValues:NO]; //only fetch the managedObjectID
NSError * error = nil;
NSArray * projectsArray = [appDelegate.managedObjectContext executeFetchRequest:allProjects error:&error];
//Delete product from Core Data
[appDelegate.managedObjectContext deleteObject:[projectsArray objectAtIndex:count]];
NSError *saveError = nil;
[appDelegate.managedObjectContext save:&saveError];
[appDelegate.projects removeObjectAtIndex:count];
NSLog(#"Removed project from Core Data");
//Insert a new object of type ProductInfo into Core Data
NSManagedObject *projectInfo = [NSEntityDescription
insertNewObjectForEntityForName:#"Project"
inManagedObjectContext:appDelegate.managedObjectContext];
//Set receipt entities values
[projectInfo setValue:newProjectName forKey:#"name"];
[projectInfo setValue:newTotalValue forKey:#"totalValue"];
if (![appDelegate.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
NSLog(#"Added Project to Core Data");
Project *tempProject = [[Project alloc] init];
tempProject.projectName = [projectInfo valueForKey:#"name"];
tempProject.totalValue = [projectInfo valueForKey:#"totalValue"];
[appDelegate.projects addObject:tempProject];
}
count++;
}
for (Category *c in appDelegate.categories){
if ([c.categoryName isEqualToString:receipt.category]){
double tempValue = [c.totalValue doubleValue];
tempValue += [receipt.amount doubleValue];
NSString *newTotalValue = [NSString stringWithFormat:#"%.02f", tempValue];
NSString *newCategoryName = c.categoryName;
//remove entity from Core Data
NSFetchRequest * allCategories = [[NSFetchRequest alloc] init];
[allCategories setEntity:[NSEntityDescription entityForName:#"Category" inManagedObjectContext:appDelegate.managedObjectContext]];
[allCategories setIncludesPropertyValues:NO]; //only fetch the managedObjectID
NSError * categoriesError = nil;
NSArray * categoriesArray = [appDelegate.managedObjectContext executeFetchRequest:allCategories error:&categoriesError];
//Delete product from Core Data
[appDelegate.managedObjectContext deleteObject:[categoriesArray objectAtIndex:catCount]];
NSError *categorySaveError = nil;
[appDelegate.managedObjectContext save:&categorySaveError];
[appDelegate.categories removeObjectAtIndex:catCount];
NSLog(#"Removed category from Core Data");
NSError * error = nil;
//Insert a new object of type ProductInfo into Core Data
NSManagedObject *categoryInfo = [NSEntityDescription
insertNewObjectForEntityForName:#"Category"
inManagedObjectContext:appDelegate.managedObjectContext];
//Set receipt entities values
[categoryInfo setValue:newCategoryName forKey:#"name"];
[categoryInfo setValue:newTotalValue forKey:#"totalValue"];
if (![appDelegate.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
NSLog(#"Added Category to Core Data");
Category *tempCategory = [[Category alloc] init];
tempCategory.categoryName = [categoryInfo valueForKey:#"name"];
tempCategory.totalValue = [categoryInfo valueForKey:#"totalValue"];
[appDelegate.categories addObject:tempCategory];
}
catCount++;
}
This code gives the error:
'...was mutated while being enumerated'.
Can anyone explain why? Also, is there a better approach to doing what I'm trying to achieve?
The error you're seeing is accurate. The problem is that you're mutating (changing) a collection while iterating over it. Basically, you're doing something of the form:
for (Project *p in appDelegate.projects) {
...
[p addObject: X]
}
This isn't allowed.
One simple solution is to make a new collection of the objects you want to add, and then add them to the original container outside of your loop. Something like:
NSMutableArray *array = [NSMutableArray array];
for (Project *p in appDelegate.projects) {
...
[array addObject:X];
}
[p addObjects:array];
By the way, did you google for the error text "was mutated while being enumerated"? I'd be surprised if you didn't find the answer for this common problem just by googling.
Also, when posting an error message, it's helpful to post the full line, not just part of it.
You are adding and removing items from appDelegate.projects while iterating over it in a for-each loop here:
[appDelegate.projects removeObjectAtIndex:count];
// ...
[appDelegate.projects addObject:tempProject];
And the same for appDelegate.categories:
[appDelegate.categories removeObjectAtIndex:count];
// ...
[appDelegate.categories addObject:tempProject];
AFAIK, in such case you should use a simple for loop, and access the array with index.