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]);
}
}];
}
Related
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.
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.
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.
I'm trying to simply search for videos using a query, which is working perfectly using the below code.
// Create a service object for executing queries
GTLServiceYouTube *service = [[GTLServiceYouTube alloc] init];
// Services which do not require sign-in may need an API key from the
// API Console
service.APIKey = #"AIzaSy...";
// Create a query
GTLQueryYouTube *query = [GTLQueryYouTube queryForSearchListWithPart:#"id,snippet"];
query.maxResults = 10;
query.q = searchBar.text;
query.videoEmbeddable = #"true";
query.type = #"video";
//query.country = #"US";
// Execute the query
GTLServiceTicket *ticket = [service executeQuery:query
completionHandler:^(GTLServiceTicket *ticket, id object, NSError *error) {
// This callback block is run when the fetch completes
if (error == nil) {
GTLYouTubeSearchListResponse *products = object;
[videoArray removeAllObjects];
// iteration of items and subscript access to items.
for (GTLYouTubeSearchResult *item in products) {
NSMutableDictionary *dictionary = [item JSONValueForKey:#"id"];
NSLog(#"%#", [dictionary objectForKey:#"videoId"]);
YoutubeVideo *video = [[YoutubeVideo alloc]init];
[video setLblTitle:item.snippet.title];
//Get youtube video image
[video setImgIconURL:[NSURL URLWithString:item.snippet.thumbnails.defaultProperty.url]];
[video setLblVideoURL:[dictionary objectForKey:#"videoId"]];
[video setLblChannelTitle:item.snippet.channelTitle];
[videoArray addObject:video];
}
reloadData = YES;
[tableView reloadData];
//Download images asynchronously
[NSThread detachNewThreadSelector:#selector(downloadImages)
toTarget:self
withObject:nil];
}else{
NSLog(#"Error: %#", error.description);
}
}];
However, now I'd like to display certain information about the video. Some of this information I can get out of
item.snippet
But I also need to get the video duration, and number of views. How can I get them using Youtube API 3.0?? I also had an idea to try using GData just for this, but it literally triples the load time to use
NSString *JSONString = [NSString stringWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"https://gdata.youtube.com/feeds/api/videos/%#?v=2&alt=json", [video lblVideoURL]]] encoding:NSUTF8StringEncoding error:nil ];
How do I get the duration of the video, plus the number of views the video has?
Search query only accept ID and Snippet as parts. If you change to Video List Query you can include other parts, but you have to use one of the filters.
I think you'll have to get the video ID with the search query and do another query (Now a video query) filtering by ID (the Id you got), than you can get all other information of the videos you searched.
The problem is i'm having trouble getting the video ID, i think the API use the word "identifier" instead of "id" because it's a reserved word of objective-c.
Edit: Yeah, it was just a matter of time, just request my GTLYoutubeSearchResponse.JSON, an manipulated it as i wanted.
FIRST QUERY:
GTLQueryYouTube *query = [GTLQueryYouTube queryForSearchListWithPart:#"id,snippet"];
query.maxResults = 10;
query.q = #"iphone";
query.fields = #"items(id,snippet)";
query.order = #"viewCount";
//query.channelId = #"UCsnbNwitAF9BzjjdMfRyK2g";//Kavaco
[appDelegate.service executeQuery:query
completionHandler:^(GTLServiceTicket *ticket,
id object,
NSError *error) {
if (error == nil) {
appDelegate.videos = object;
[self performSegueWithIdentifier:#"videoList" sender:self];
}
else {
NSLog(#"%#", error.description);
}
}];
SECOND QUERY: In my TableViewController, inside my cellForRowAtIndexPath i do another query for each video i found. Be sure to request only the variables you need to avoid spending your credits, in my case i requested only viewCount.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"myCell" forIndexPath:indexPath];
GTLYouTubeVideo *video = appDelegate.videos[indexPath.row];
NSMutableDictionary *videoIdJson = [video.JSON objectForKey:#"id"];
NSString *videoId = [videoIdJson objectForKey:#"videoId"];
cell.textLabel.text = video.snippet.title;
GTLQueryYouTube *query = [GTLQueryYouTube queryForVideosListWithPart:#"statistics"];
query.identifier = videoId;
query.maxResults = 1;
query.fields = #"items/statistics(viewCount)";
[appDelegate.service executeQuery:query
completionHandler:^(GTLServiceTicket *ticket,
id object,
NSError *error) {
if (error == nil) {
GTLYouTubeVideoListResponse *detalhe = object;
NSMutableDictionary *responseJSON = detalhe.JSON;
NSArray *tempArray = [responseJSON objectForKey:#"items"];
NSMutableDictionary *items = tempArray[0];
NSMutableDictionary *statistics = [items objectForKey:#"statistics"];
_views = [[NSString alloc] initWithFormat:#"Views: %#",[statistics objectForKey:#"viewCount"]];
cell.detailTextLabel.text = _views;
}
else {
NSLog(#"%#", error.description);
}
}];
cell.detailTextLabel.text = _views;
return cell;
}
Hope it helps.
Collect the id from search API and do another video list API call is the proper way to do what you want to achieve. The video list API call can put multiple video ids separate by comma in the same call. The extra call shouldn't consider exhausting because this is expected behavior on API v3:
Project Member #1 je...#google.com
That's the expected behavior, and not likely to change. Since the
search.list() method can return channels, videos, and playlists, only
properties that make sense for all of those resource types are
returned in the search responses. If you need to obtain any other
properties, making a follow-up request to, e.g., videos.list() is
required. Note that you can pass in up to 50 video ids to
videos.list(), so you can effectively look up an entire page's worth
of search.list() results in a single video.list() call.
If you try https://developers.google.com/youtube/v3/docs/videos/list#try-it , you set contentDetails,statistics as the part, you should able to get the following result:
"contentDetails": {
"duration": "PT20M38S",
"dimension": "2d",
"definition": "hd",
"caption": "false",
"licensedContent": false
},
"statistics": {
"viewCount": "191",
"likeCount": "7",
"dislikeCount": "0",
"favoriteCount": "0",
"commentCount": "0"
}
PT20M38S means 20 minutes and 38 seconds, based on ISO 8601(http://en.wikipedia.org/wiki/ISO_8601)
The best way for make this is:
if (!service) {
service = [[GTLServiceYouTube alloc] init];
service.shouldFetchNextPages = YES;
service.shouldFetchInBackground = YES;
service.retryEnabled = YES;
service.APIKey = #"AIzaSyDSO2JPnM_r9VcDrDJJs_d_7Li2Ttk2AuU";
}
[youtubeList removeAllObjects];
GTLQueryYouTube *query = [GTLQueryYouTube queryForSearchListWithPart:#"id"];
query.maxResults = 50;
query.q = withText;
query.fields = #"items(id)";
query.order = #"viewCount";
query.type = #"video";
// query.videoDuration = #"long";//any-long-medium-short
__block NSInteger incrementRequest = 0;
[service executeQuery:query completionHandler:^(GTLServiceTicket *ticket, id object, NSError *error) {
if (error) {
NSLog(#"Error is!! = %#", error.localizedDescription);
return;
}
GTLYouTubeVideoListResponse *idsResponse = object;
for (GTLYouTubeVideoListResponse *videoInfo in object) {
[youtubeList addObject:videoInfo.JSON];
GTLQueryYouTube *query2 = [GTLQueryYouTube queryForVideosListWithIdentifier:[[videoInfo.JSON valueForKey:#"id"] valueForKey:#"videoId"] part:#"id,contentDetails,snippet,statistics"];
query2.maxResults = 1;
query2.fields = #"items(id,contentDetails,snippet,statistics)";
query2.order = #"viewCount";
[service executeQuery:query2 completionHandler:^(GTLServiceTicket *ticket, id object, NSError *error) {
if (error) {
NSLog(#"Error is!! = %#", error.localizedDescription);
return;
}
GTLYouTubeVideoListResponse *detalhe = object;
for (NSMutableDictionary *tmpDict in youtubeList) {
if ([[[tmpDict valueForKey:#"id"] valueForKey:#"videoId"] isEqualToString:[[[detalhe.JSON valueForKey:#"items"] objectAtIndex:0] valueForKey:#"id"]]) {
[tmpDict removeObjectForKey:#"id"];
//condition personal
if (![Utils parseISO8601TimeIsGrater30:[[[[detalhe.JSON valueForKey:#"items"] objectAtIndex:0] valueForKey:#"contentDetails"] valueForKey:#"duration"]]) {
BOOL isBlockedInUs = NO;
for (NSString *countryRestric in [[[[[detalhe.JSON valueForKey:#"items"] objectAtIndex:0] valueForKey:#"contentDetails"] valueForKey:#"regionRestriction"] valueForKey:#"blocked"]) {
if ([countryRestric isEqualToString:#"US"]) {
isBlockedInUs = YES;
break;
}
}
if (!isBlockedInUs) {
[tmpDict addEntriesFromDictionary:detalhe.JSON];
[tmpDict setValue:[[[[detalhe.JSON valueForKey:#"items"] objectAtIndex:0] valueForKey:#"snippet"] valueForKey:#"publishedAt"] forKey:#"publishedAt"];
} else {
[youtubeList removeObject:tmpDict];
}
} else {
[youtubeList removeObject:tmpDict];
}
break;
}
}
incrementRequest ++;
if ([idsResponse.items count] == incrementRequest) {
//Finish
[self.tableView reloadData];
}
}];
}
}];
I've been working on adding Core Data into my iPhone app, and I've been running into some very frustrating issues. When I call save on my context, the save returns successfully, however no data is getting added to my database(I am running this on the simulator and looking at the SQLITE file to check).
I am using the MYDocumentHandler class from this post to use a single UIManagedDocument across multiple classes. I run the code in my AppDelegate as follows:
if (!self.document) {
[[MYDocumentHandler sharedDocumentHandler] performWithDocument:^(UIManagedDocument *document) {
self.document = document;
self.context = document.managedObjectContext;
[self loadView];
}];
}
The loadView method setups up my view controllers once the document has been returned. In my view controllers that use the Core Data I again use something like this:
- (void)viewDidLoad
{
[super viewDidLoad];
if(!self.document){
[[MYDocumentHandler sharedDocumentHandler] performWithDocument:^(UIManagedDocument *document) {
self.document = document;
self.context = document.managedObjectContext;
[self loadAll];
}];
}
}
Where the loadAll method setups everything for the view. When I try to save my data, I use the following:
for (int i = 0; i < [jsonArray count]; i++) {
NSDictionary *dictionary = [jsonArray objectAtIndex:i];
ProjectObject *tempProject = [[ProjectObject alloc] initWithDict:dictionary andETag:etag];
[tempAllProjects addObject:[Projects newProject:tempProject withContext:self.context]];
[tempProject release];
}
[self saveWithContext:self.context];
My saveWithContext method looks like this:
- (BOOL) saveWithContext:(NSManagedObjectContext *)context{
NSError *error = nil;
if (context != nil) {
if ([context hasChanges] && ![context save:&error]) {
DLog(#"Unresolved error %#, %#", error, [error userInfo]);
} else{
DLog(#"save was successful");
return YES;
}
}else{
DLog(#"context is nil");
return NO;
}
}
I always get the save was successful message, and I get the proper messages from the MYDocumentHandler file. Unfortunately, the data is simply not making it to the database. The data is definitely stored in the context, but its not going to the database. Any ideas?
Edit:
Here is the code where I create the Entities:
+ (Projects *) newProject:(ProjectObject *)project withContext:(NSManagedObjectContext *)context
{
Projects *newProject = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Projects"];
request.predicate = [NSPredicate predicateWithFormat:#"project_id = %#", project.project_id];
NSArray *results = [context executeFetchRequest:request error:nil];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if([results count] > 0){
newProject = [results objectAtIndex:0];
newProject.account_id = [NSString stringWithFormat:#"%#", [defaults objectForKey:#"account_id"]];
} else {
newProject = [NSEntityDescription insertNewObjectForEntityForName:#"Projects" inManagedObjectContext:context];
}
newProject.account_id = [NSString stringWithFormat:#"%#", [defaults objectForKey:#"account_id"]];
newProject.project_id = project.project_id;
newProject.name = project.name;
newProject.project_description = project.description;
newProject.updated_at = project.updated_at;
newProject.starred = project.starred;
newProject.etag = project.etag;
newProject.synced = [self hasConnection] ? [NSNumber numberWithInt:1] : [NSNumber numberWithInt:0];
return newProject;
}
I found the solution in this post. Not sure if this is the best way to handle it. If there is a better option, please let me know.
You are not saving Manage object context after changing it. I have added the lines into your code.
Please check if it works now.