We're currently trying to get HealthKit to work in the background, in order to deliver steps data to our server when the App is closed.
For experimental purposes we've created a brand new iOS project in XCode, enabled HealhtKit and all background modes in Compabilities. After that, we pretty much run the code (see further down).
So what happens first is that the app ofcourse asks for the permissions, which we grant. What we're expecting is that the app should keep deliver the steps data every hour, to the server. But it doesnt do that, it seems like the app cant do anything when it's not active.
The app only deliver data when it gets resumed or started, but not at all from the background (Soft-closed / Hard-closed)
appdelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self setTypes];
return YES;
}
-(void) setTypes
{
self.healthStore = [[HKHealthStore alloc] init];
NSMutableSet* types = [[NSMutableSet alloc]init];
[types addObject:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]];
[self.healthStore requestAuthorizationToShareTypes: types
readTypes: types
completion:^(BOOL success, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self observeQuantityType];
[self enableBackgroundDeliveryForQuantityType];
});
}];
}
-(void)enableBackgroundDeliveryForQuantityType{
[self.healthStore enableBackgroundDeliveryForType: [HKQuantityType quantityTypeForIdentifier: HKQuantityTypeIdentifierStepCount] frequency:HKUpdateFrequencyImmediate withCompletion:^(BOOL success, NSError *error) {
}];
}
-(void) observeQuantityType{
HKSampleType *quantityType = [HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
HKObserverQuery *query =
[[HKObserverQuery alloc]
initWithSampleType:quantityType
predicate:nil
updateHandler:^(HKObserverQuery *query,
HKObserverQueryCompletionHandler completionHandler,
NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (completionHandler) completionHandler();
[self getQuantityResult];
});
}];
[self.healthStore executeQuery:query];
}
-(void) getQuantityResult{
NSInteger limit = 0;
NSPredicate* predicate = nil;
NSString *endKey = HKSampleSortIdentifierEndDate;
NSSortDescriptor *endDate = [NSSortDescriptor sortDescriptorWithKey: endKey ascending: NO];
HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType: [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]
predicate: predicate
limit: limit
sortDescriptors: #[endDate]
resultsHandler:^(HKSampleQuery *query, NSArray* results, NSError *error){
dispatch_async(dispatch_get_main_queue(), ^{
// sends the data using HTTP
[self sendData: [self resultAsNumber:results]];
});
}];
[self.healthStore executeQuery:query];
}
I found this out a little while ago when talking to someone from Apple. Apparently you can't access HK data in the background if the device is locked:
NOTE
Because the HealthKit store is encrypted, your app cannot read data
from the store when the phone is locked. This means your app may not
be able to access the store when it is launched in the background.
However, apps can still write data to the store, even when the phone
is locked. The store temporarily caches the data and saves it to the
encrypted store as soon as the phone is unlocked.
from:
https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HealthKit_Framework/
I see something that might be causing an issue in your AppDelegate, particularly this line:
[[NSURLConnection alloc] initWithRequest:request delegate:self];
This is creating an NSURLConnection, but not starting it. Try changing it to this:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
Edit: After taking a second look at the docs
They recommend setting up your observer queries in your application didFinishLaunchingWithOptions: method. In your code above, you set the HKObserverQuery up in the authorization handler, which is called on a random background queue. Try making this change to set it up on the main thread:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self setTypes];
[self observeQuantityType];
return YES;
}
HKObserverQuery Reference
Related
After I update my Core Data store - by deleting then adding the data - in a different thread, I'm required to change the screen and then go back for the data to update it. Is there a way to do update Core Data without having to change the screen in the app?
code to reset database:
- (void) resetDatabase {
count++;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
ConDAO *con = [[ConDAO alloc] init];
DatabaseManager *manager = [DatabaseManager sharedManager];
NSError * error;
NSURL * storeURL = [[[manager managedObjectContext] persistentStoreCoordinator] URLForPersistentStore:[[[[manager managedObjectContext] persistentStoreCoordinator] persistentStores] lastObject]];
[[manager managedObjectContext] reset];//to drop pending changes
if ([[[manager managedObjectContext] persistentStoreCoordinator] removePersistentStore:[[[[manager managedObjectContext] persistentStoreCoordinator] persistentStores] lastObject] error:&error])
{
// remove the file containing the data
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:&error];
//recreate the store like in the appDelegate method
[[[manager managedObjectContext] persistentStoreCoordinator] addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];//recreates the persistent store
}
NSLog(#"*****************************");
NSLog(#"updating");
NSLog(#"count: %d", count);
NSLog(#"*****************************");
[self populateDatabase:0 con:con];
NSTimer *timer = [NSTimer timerWithTimeInterval:60.0
target:self
selector:#selector(resetDatabase)
userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
dispatch_async(dispatch_get_main_queue(), ^(void){
});
});
}
Code that runs when ui is changed:
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
// Setup KVO for verifyingcard
[self addObserver:self forKeyPath:#"verifyingCard" options:NSKeyValueObservingOptionNew context:nil];
if([BluetoothTech isEqualToString:#"BLE"]){
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:#{CBCentralManagerOptionShowPowerAlertKey: #YES}];
}
else if([BluetoothTech isEqualToString:#"HID"]){
[self.bluetoothScanTextView becomeFirstResponder];
}
[self loadStudents];
}
I think it has to do with the loadStudents() function, but when I use NSNotificationCenter to run it from the other class, it still doesnt work.
LoadStudent code:
- (void)loadStudents{
NSError *error = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Caf_student_cards"];
NSArray *arr = [[self.manager managedObjectContext] executeFetchRequest:fetchRequest error:&error];
for(int i = 0; i < [arr count]; i++){
if([[[arr objectAtIndex:i] valueForKey:#"user_id"] isEqualToString:#"201509061"]){
NSLog(#"%#",[arr objectAtIndex:i]);
}
}
if(!error){
self.caf_student_cards = [NSMutableArray arrayWithArray:arr];
self.keys = [[[arr.firstObject entity] attributesByName] allKeys];
}
else{
NSLog(#"%s %s %s","\n\n\n",[[error localizedDescription] UTF8String],"\n\n\n");
dispatch_async(dispatch_get_main_queue(), ^{
// Show alert to tell user to reload this page
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:#"Error: %#", [error localizedDescription]] message:#"Check connection and relog back into cafeteria." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alert show];
});
}
}
There are multiple problems with this code.
The one most directly relevant to your question is that you're updating your persistent store, but not updating your UI. It doesn't happen automatically. If you have new data, you need to tell your UI to update. How to do that depends on what kind of UI you have. If it's a table view, it might be as simple as telling the table view to reload its data. If you have an array that you use to hold the UI's data, you would need to update that too (it looks like this might be what caf_student_cards is in your code but it's impossible to be sure.
Other problems-- and these are major problems that you need to fix immediately:
You're doing Core Data multithreading wrong. Using dispatch_async is not effective here. You need to use performBlock or performBlockAndWait on your managed object context, or else performBackgroundTask on your persistent container.
You're removing the main persistent store file but not removing the journal files. This is pretty much guaranteed to either prevent old data from being deleted or else simply cause data corruption. What you're doing is not a useful technique. Removing the persistent store files is rarely a good idea. If you want to get rid of existing data, delete it from Core Data, maybe by telling your context to delete the objects or else by using NSBatchDeleteRequest.
There may be others. This code is a mess. You would be doing yourself a huge favor if you spent a little time looking over Apple's Core Data Programming Guide.
Also you keep asking nearly the same question repeatedly. You've had some good advice, but you don't seem to be taking any of it. If you want more information, go and read over other answers people have already given when you've posted this question before.
From what I've been reading about the largely undocumented NSAsynchronousFetchRequest, it is supposed to be cancelable. In Apple's video "What's New in Core Data" from WWDC 2014, there is an example of it being done (right around 17:40). But nowhere have I found how this is supposed to be done.
I've tried setting it up to cancel a fetch when a new fetch comes in, but I have been, seemingly, unsuccessful in getting this to work. The reason I say "seemingly" is because when I debug the code, it hits the cancel method of NSAsyncronousFetchResult's NSProgress property (and the property is not nil). However, after several previous fetches have been "cancelled" the app freezes for approximately the amount of time it would have taken to perform all the fetches. So, it doesn't seem like the fetches are being canceled. Here is what I am trying to cancel the fetch:
if (self.asyncFetchResult) {
[self.asyncFetchResult.progress cancel];
}
NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"OfflineFeature"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"layers.layerName in %# AND xMax >= %lf AND xMin <= %lf AND yMax >= %lf AND yMin <=%lf", allLayerNames, bufferedEnvelope.xmin,bufferedEnvelope.xmax,bufferedEnvelope.ymin,bufferedEnvelope.ymax];
NSAsynchronousFetchRequest* asyncFetchRequest = [[NSAsynchronousFetchRequest alloc] initWithFetchRequest:fetchRequest completionBlock:^(NSAsynchronousFetchResult* result) {
if (![result.progress isCancelled]) {
allFeatures = result.finalResult;
dispatch_async(dispatch_get_main_queue(), ^{
//Bunch of code to use the results
});
}
}];
MVAppDelegate* appDelegate = (MVAppDelegate*)[[UIApplication sharedApplication] delegate];
__weak typeof(self) weakSelf = self;
[appDelegate.managedObjectContext performBlock:^{
NSProgress* progress = [NSProgress progressWithTotalUnitCount:1];
[progress becomeCurrentWithPendingUnitCount:1];
NSError* error;
weakSelf.asyncFetchResult = [appDelegate.managedObjectContext executeRequest:asyncFetchRequest error:&error];
if (error) {
NSLog(#"Error performing asynchronous fetch request.\n%#", error);
}
[progress resignCurrent];
}];
I would appreciate any thoughts on what I'm doing wrong or if there's something else I could try that may be more appropriate. Thanks in advance.
I am working on my first iPhone App: a simple app showing the heartRate results from HealthKit in a nice way. My first step is to show the results as a raw text. But unfortunately I'm getting an exception at the following line, telling me: "thread 1 signal SIGABRT". Does someone know, what I did wrong and hint me in a direction?
double usersBeatsPerMinute = [quantity doubleValueForUnit:[HKUnit countUnit]];
The rest of the code looks like this:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// Set up an HKHealthStore, asking the user for read/write permissions. The profile view controller is the
// first view controller that's shown to the user, so we'll ask for all of the desired HealthKit permissions now.
// In your own app, you should consider requesting permissions the first time a user wants to interact with
// HealthKit data.
if ([HKHealthStore isHealthDataAvailable]) {
NSSet *writeDataTypes = [self dataTypesToWrite];
NSSet *readDataTypes = [self dataTypesToRead];
[self.healthStore requestAuthorizationToShareTypes:writeDataTypes readTypes:readDataTypes completion:^(BOOL success, NSError *error) {
if (!success) {
NSLog(#"You didn't allow HealthKit to access these read/write data types. In your app, try to handle this error gracefully when a user decides not to provide access. The error was: %#. If you're using a simulator, try it on a device.", error);
return;
}
}];
}
HKQuantityType *weightType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
// Since we are interested in retrieving the user's latest sample
// we sort the samples in descending order by end date
// and set the limit to 1
// We are not filtering the data, and so the predicate is set to nil.
NSSortDescriptor *timeSortDescriptor = [[NSSortDescriptor alloc] initWithKey:HKSampleSortIdentifierEndDate ascending:NO];
// construct the query & since we are not filtering the data the predicate is set to nil
HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:weightType predicate:nil limit:1 sortDescriptors:#[timeSortDescriptor] resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
// if there is a data point, dispatch to the main queue
if (results) {
dispatch_async(dispatch_get_main_queue(), ^{
HKQuantitySample *quantitySample = results.firstObject;
// pull out the quantity from the sample
HKQuantity *quantity = quantitySample.quantity;
double usersBeatsPerMinute = [quantity doubleValueForUnit:[HKUnit countUnit]];
_HeartRateResults.text = [NSString stringWithFormat:#"%# lbs", [NSNumberFormatter localizedStringFromNumber:#(usersBeatsPerMinute) numberStyle:NSNumberFormatterNoStyle]];
});
}
}];
// do not forget to execute the query after its constructed
[_healthStore executeQuery:query];}
There was a comment in the documentation ("These samples use count/time units") I didn't quite understand, so I did a little searching and tried it out and was able to get a value I manually put into the Health app using this:
double rate = [mostRecentQuantity doubleValueForUnit:[[HKUnit countUnit] unitDividedByUnit:[HKUnit minuteUnit]]];
I haven't seen unitDividedByUnit before. Here's the article I pulled it from.
I do a Parse query in my iPhone app, but I am getting errors trying to do the same Parse query in my Watch app.
This is the query in my iPhone app:
- (void)viewDidLoad {
// GMT Date from Phone
NSDate *gmtNow = [NSDate date];
NSLog(#"GMT Now: %#", gmtNow);
// Query Parse
PFQuery *query = [self queryForTable];
[query whereKey:#"dateGame" greaterThanOrEqualTo:gmtNow];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
NSMutableArray *localMatchup = [#[] mutableCopy];
for (PFObject *object in objects) {
// Add objects to local Arrays
[localMatchup addObject:[object objectForKey:#"matchup"]];
// App Group
NSString *container = #"group.com.me.off";
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:container];
// Matchup
[defaults setObject:localMatchup forKey:#"KeyMatchup"];
NSArray *savedMatchup = [defaults objectForKey:#"KeyMatchup"];
NSLog(#"Default Matchup: %#", savedMatchup);
savedMatchup = matchupArray;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
}];
}
This is the query I tried in my WatchKit app...
- (void)awakeWithContext:(id)context {
// GMT Date from Phone
NSDate *gmtNow = [NSDate date];
NSLog(#"GMT Now: %#", gmtNow);
// Query Parse
PFQuery *query = [self queryForTable];
[query whereKey:#"dateGame" greaterThanOrEqualTo:gmtNow];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
NSMutableArray *localMatchup = [#[] mutableCopy];
for (PFObject *object in objects) {
// Add objects to local Arrays
[localMatchup addObject:[object objectForKey:#"matchup"]];
// App Group
NSString *container = #"group.com.me.off";
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:container];
// Matchup
[defaults setObject:localMatchup forKey:#"KeyMatchup"];
NSArray *savedMatchup = [defaults objectForKey:#"KeyMatchup"];
NSLog(#"Default Matchup: %#", savedMatchup);
savedMatchup = self.matchupArray;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
}];
}
But I get errors on these two lines...
`PFQuery *query = [self queryForTable];`
`[self.tableView reloadData];`
because I can't do the same exact code related to a table I'm guessing, but I'm just not sure what to change it to.
EDIT: Adding code per #cnoon answer
WatchKit InterfaceController.m:
How would I ask for my query to run here?
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
[WKInterfaceController openParentApplication:nil reply:^(NSDictionary *replyInfo, NSError *error) {
// What to put here?
NSLog(#"Open Parent Application");
}];
-and-
iPhone AppDelegate.h
How would I ask for my PFQuery to run?
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply {
// What to put here?
}
There's no such thing as a UITableView in a WatchKit application. Instead, you have to work with WKInterfaceTable. Before you continue, I'd also suggest you read through the documentation in the WatchKit Programming Guide. It will give you a MUCH better understanding of all the toolsets available to you as an aspiring Apple Watch developer.
WKInterfaceTable
Once you know the ins and outs of a WKInterfaceTable, you'll quickly see why your approach is flawed for two reasons. First off, you don't have a reloadData method. The alternative in WatchKit is to setNumberOfRows(_:withRowTypes:). Then you need to iterate through each row and configure it.
PFQuery in WatchKit Extension
The second reason you are going to have issues is due to your use of PFQuery.
This is a bit of side advice, so take it or leave it. I'm speaking from experience here having already built a very large page-based Watch App that heavily communicates with the iOS App.
I would advise you to stop making PFQuerys in your WatchKit Extension. The reason is that users using your Watch App are only going to have the app open for a second or two. Everything will happen extremely fast. Because of this, it is extremely difficult to guarantee the success of network calls before the Watch App is terminated by the user. This makes things MUCH more difficult, but is simply the way it is.
Instead, you want to run your PFQuery calls on the iOS App and return that information back to the Watch Extension through the following calls:
WKInterfaceController - openParentApplication(_:reply:)
UIApplicationDelegate - handleWatchKitExtensionRequest(_:reply:)
You can also cache the PFQuery into the shared app group using MMWormhole or a similar approach alternative. Below is an example of how you can have your Watch Extension request the iOS Application to run a PFQuery, cache the data in MMWormhole and notify the Watch Extension once it is finished. By always reading the data out of the cache, you have a consistent mechanism whether the Watch Extension was still running as well as closed and re-opened.
Objective-C
InterfaceController.m
- (void)willActivate {
[super willActivate];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[WKInterfaceController
openParentApplication:#{#"pfquery_request": #"dumm_val"}
reply:^(NSDictionary *replyInfo, NSError *error) {
NSLog(#"User Info: %#", replyInfo);
NSLog(#"Error: %#", error);
if ([replyInfo[#"success"] boolValue]) {
NSLog(#"Read data from Wormhole and update interface!");
}
}];
});
}
AppDelegate.m
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply {
if (userInfo[#"pfquery_request"]) {
NSLog(#"Starting PFQuery"); // won't print out to console since you're running the watch extension
// 1. Run the PFQuery
// 2. Write the data into MMWormhole (done in PFQuery completion block)
// 3. Send the reply back to the extension as success (done in PFQuery completion block)
reply(#{#"success": #(YES)});
}
reply(#{#"success": #(NO)});
}
Swift
InterfaceController.swift
override func willActivate() {
super.willActivate()
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2.0 * Float(NSEC_PER_SEC))), dispatch_get_main_queue()) {
WKInterfaceController.openParentApplication(["pfquery_request": "dummy_val"]) { userInfo, error in
println("User Info: \(userInfo)")
println("Error: \(error)")
if let success = (userInfo as? [String: AnyObject])?["success"] as? NSNumber {
if success.boolValue == true {
println("Read data from Wormhole and update interface!")
}
}
}
return
}
}
AppDelegate.swift
func application(
application: UIApplication!,
handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!,
reply: (([NSObject : AnyObject]!) -> Void)!)
{
if let pfqueryRequest: AnyObject = (userInfo as? [String: AnyObject])?["pfquery_request"] {
println("Starting PFQuery") // won't print out to console since you're running the watch extension
// 1. Run the PFQuery
// 2. Write the data into MMWormhole (done in PFQuery completion block)
// 3. Send the reply back to the extension as success (done in PFQuery completion block)
reply(["success": true])
}
reply(["success": false])
}
Hopefully that helps break down the complexity of having a consistent way to read data from the cache as well as offload network requests (or PFQueries) to the iOS App.
Im still trying to figure out what loads the UI thread. In a class(a child of UITableView) there's a FRC:
NSFetchRequest *request = [DEPlace MR_requestAllWithPredicate:[NSPredicate predicateWithFormat:#"isWorking == YES"]];
request.sortDescriptors = #[ [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES] ];
self.placesController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:[NSManagedObjectContext MR_rootSavingContext]
sectionNameKeyPath:nil
cacheName:nil];
self.placesController.delegate = self;
It used to be attached to a MR_contextForCurrentThread. Changing it to rootSavingContext slightly affected the performance. Then i set both root and default contexts to the same one:
[NSManagedObjectContext MR_setRootSavingContext:managedObjectStore.persistentStoreManagedObjectContext];
[NSManagedObjectContext MR_setDefaultContext:managedObjectStore.persistentStoreManagedObjectContext];
Default context used to be set to mainQueueManagedObjectContext. I want to move literally everything core data related to background and let FRC take care of interactions with the UI. FRC delegate gets new data by:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
//self.places = [self sortPlaces:controller.fetchedObjects];
self.places = controller.fetchedObjects;
[self.delegate contentUpdatedInDatasource:self];
}
I disabled the sorting by now, thought it could affect the main thread. I've tried figuring out what else could load the main thread with Time Profiler, but didn't find anything suspicious. screenshot
When all the data is loaded everything run smoothly, the app lags only at the first start, when the DB gets populated. Since everything loading-related is held by RestKit i don't think it causes problems.
I was thinking of delaying requests by 10 per second max, but have no idea how can i achieve it. Basically, on the start app gets and array of IDs(~250 by now) and then looping trough the array and requesting data from the server by each ID. It's not so crucial so far, but when the array grow up to 1-2k it would be a big problem. Btw, a single data object has 4 relationships in the DB. Is reducing dependencies a possible solution?
UPDATE:
I've tried to split the request to 1 by 1 and it caused a pretty weird behaviour.
For some reason there's a huge delay between requests.
This is how i get an array of IDs
AFJSONRequestOperation *op = [[AFJSONRequestOperation alloc] initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[APIRoot stringByAppendingFormat:#"/venues/listId?%#=%#&%#=%#", TokenKey, [DEUser token], UDIDKey, [DEUser udid]]]]];
// dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.name.bgqueue", NULL);
op.successCallbackQueue = backgroundQueue;
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//gettin an array of IDs
NSArray *array = (NSArray*) responseObject;
if(array.count)
{
_array = array;
[self getVenuesFromSelfArrayWithCurrentIndex:0];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"3rr0r: %#", error);
}];
[[NSOperationQueue mainQueue] addOperation:op];
And this is a code of recursive method:
- (void)getVenuesFromSelfArrayWithCurrentIndex: (NSUInteger)index
{
if(index >= _array.count){ NSLog(#"loading finished!"); return; }
//version of the app, location e.t.c.
NSMutableDictionary *options = [[self options] mutableCopy];
[options setObject:[_array objectAtIndex:index] forKey:#"venueId"];
//method below calls RKs getObjectsAtPath, and it's pretty much the only thing it does
[[DEAPIService sharedInstance] getObjectsOfClass:[DEPlace class]
withOptions:options
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult){
NSManagedObject *object = [mappingResult.array firstObject];
if([object isKindOfClass:[DEPlace class]])
{
[self getVenuesFromSelfArrayWithCurrentIndex:index+1];
}
} failure:^(RKObjectRequestOperation *operation, NSError *error){
NSLog(#"Failed to load the place with options: %#", options.description);
[self getVenuesFromSelfArrayWithCurrentIndex:index+1];
}];
}
The weird part is that it takes ~1-2 seconds(!) to start next request and cpu usage log and threads look.. strange.
Screenshot 1
Screenshot 2
Any suggestions?
At this point in time I can only suggest around the 250 requests. You can't make more than around 4 or 5 concurrent network requests without flooding the network and grinding it to a halt on a mobile device. Really you should change the web service design so you can send batch requests as this is a lot more efficient both for the client and the server.
Anyway, you can limit the concurrent requests by setting the maxConcurrentOperationCount of the operationQueue of your object manager. The recommendation would be to set it to 4.