I guess this is really a feature request to Google, but I'm curious if anyone knows a work around. I'd like to execute a synchronous query request to a GAE Endpoints api.
In Android executing a request is synchronous. Then you put it into an AsyncTask to make it work in the background.
In iOS executing a request is asynchronous. You simply pass in a callback block.
I'm converting an Android app into an iOS app and it'd be really nice if they used the same mechanism. For example there are times when I WANT a synchronous query. It just makes my code easier and I know to put it on a background thread.
So my question is this... is there any way (hacky or not) to block until the iOS query completes?
You can wait on the call to finish with code with a timeout using code similar to this. Obviously you wouldn't want to do this on a UI thread but this would ensure your completion handlers run in serial.
NSLog(#"Before API Call");
GTLServiceTicket *apiCall = [apiService executeQuery:query completionHandler:^(GTLServiceTicket *ticket,
GTLHelloworldHelloGreeting *object,
NSError *error) {
NSLog(#"Starting completion handler");
NSArray *greetings = [NSArray arrayWithObjects: object, nil];
greetingsRetrievedFromAPI = greetings;
[self performSegueWithIdentifier: #"DisplayGreetings" sender: self];
NSLog(#"Ending completion handler");
}];
[apiService waitForTicket:apiCall timeout:100000 fetchedObject:nil error:nil];
NSLog(#"After completion handler");
Related
I am trying to take two pictures and want them to run in order. The method for capturing pictures is done using a method that runs asynchronously. I currently use semaphores to try to synchronize this, but it stops the program from running. I believe this is because the completionHandler is run on the same thread, and since the thread is locked, it can't execute to free the semaphore.
Here is my current code:
camera_sema = dispatch_semaphore_create(0);
[photoOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error)
{
[flash turnOffFlash];//turning off flash for second picture
//perform needed work
dispatch_semaphore_signal(camera_sema);
}];
dispatch_semaphore_wait(camera_sema, DISPATCH_TIME_FOREVER);
Is there a way I can use dispatch_async for this case or a way to run the completionHandler on another background thread so it is able to execute?
UPDATE 1:
This works but not sure if its the best way to do it.
-(void*)method: (int) iteration {
[photoOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error)
{
[flash turnOffFlash];//turning off flash for second picture
//perform needed work
//base case
[method: iteration-1];
}];
}
So basically I call the method recursively to ensure that it only repeats once the callback has been reached. It will essentially run an async task synchronously. Unfortunately, for the final design, I need to be able to run it infinitely. Doing this recursively will fail. Please let me know if there is better way to do this.
I'm currently working on an iOS project that utilises the AWS SDK to download large media files to the device. I am using CloudFront to distribute the content and the downloads are working well, however I am having problems implementing a network queue for these operations. No matter what I try, all the files want to download at once.
I am using the AWSContent downloadWithDownloadType: method to initiate and monitor progress on the actual downloads.
I have tried using an NSOperationQueue and setting setMaxConcurrentOperationCount, and all the code blocks execute at once. :(
I have a feeling it might be configurable with AWSServiceConfiguration in the AppDelegate, but the documentation is extremely vague on what variables you can pass into that object... http://docs.aws.amazon.com/AWSiOSSDK/latest/Classes/AWSServiceConfiguration.html
Has anyone had any experience with this?
TIA
Your problem is most likely that you misunderstand an approach of asynchronous operations.
I have tried using an NSOperationQueue and setting
setMaxConcurrentOperationCount, and all the code blocks execute at
once. :(
It's difficult to say what's definitely wrong without seeing an actual code, however most likely it's tied to the following steps:
You create NSOperationQueue
You set maxConcurrentOperationsCount to 2 for example
You add 4 blocks to it with AWSContent downloadWithDownloadType:
You expect no more 2 downloads to be run simultaneously
What do you probably do wrong
The key is inside point 3. What exactly the block does? My guess is that it completes before actual download completes. So if you have something like:
NSOperationQueue *queue = [NSOperationQueue new];
queue.maxConcurrentOperationsCount = 2;
for (AWSContent *content in contentArray) { // Assume you already do have this array
[queue addOperationWithBlock:^() {
[content downloadWithDownloadType:AWSContentDownloadTypeIfNotCached
pinOnCompletion:YES
progressBlock:nil
completionHandler:^(AWSContent *content, NSData *data, NSError *error) {
// do some stuff here on completion
}];
}];
}
Your block exits before your download is finished, allowing next blocks to run on queue and starting further downloads.
What to try
You should simply add some synchronization mechanism to your block to let operation complete only on completion block. Say:
NSOperationQueue *queue = [NSOperationQueue new];
queue.maxConcurrentOperationsCount = 2;
for (AWSContent *content in contentArray) { // Assume you already do have this array
[queue addOperationWithBlock:^() {
dispatch_semaphore_t dsema = dispatch_semaphore_create(0);
[content downloadWithDownloadType:AWSContentDownloadTypeIfNotCached
pinOnCompletion:YES
progressBlock:nil
completionHandler:^(AWSContent *content, NSData *data, NSError *error) {
// do some stuff here on completion
// ...
dispatch_semaphore_signal(dsema); // it's important to call this function in both error and success cases of download to free the block from queue
}];
dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER); // or another dispatch_time if you want your custom timeout instead of AWS
}];
}
Effectively your answer is https://stackoverflow.com/a/4326754/2392973
You just schedule plenty of such blocks to your operation queue.
More reading
https://developer.apple.com/reference/dispatch
I use the code below to have my app detect updates to HealthKit data in the background. Will the init method of my AppDelegate be called when this code is run in the background? What methods in the AppDelegate will be called? If someone can provide documentation about the application lifecycle of background code, that will be much appreciated!
[healthStore enableBackgroundDeliveryForType:type frequency:HKUpdateFrequencyHourly withCompletion:^(BOOL success, NSError *error) {
if (success) {
HKObserverQuery *observerQuery = [[HKObserverQuery alloc] initWithSampleType:type
predicate:nil
updateHandler:^(HKObserverQuery *query, HKObserverQueryCompletionHandler completionHandler, NSError *error) {
if (!error) {
[self retrieveHealthDataWithCompletionHandler:completionHandler];
}
}];
[healthStore executeQuery:observerQuery];
}
A bit late but hopefully, it would still help you or anyone else who reach here..
When your app delegate’s application:didFinishLaunchingWithOptions: method is being called you can assume the app launches. Thats why Apple recommend you'll register any observer queries you'd like to have inside that method.
When there will be new data of the type you registered for, HealthKit will wake your app. (So far you still don't know anything about any new data.) Once your app finish its launching it will call the beloved app delegate’s application:didFinishLaunchingWithOptions: method, which as stated before, should contain the code of registering the observer queries.
Once you register your queries, next thing will be getting an update about new data (this is the purpose of observer queries).
Getting the update about something new in HealthKit doesn't contain the data itself. Thats why in the updateHandler of the observer query you should initiate anther query - a more specific one that will fetch the wanted data.
That's it. I would make some changes in the code you provided in order for it to work:
[healthStore enableBackgroundDeliveryForType:type frequency:HKUpdateFrequencyHourly withCompletion:^(BOOL success, NSError *error) {
if (success) {
//Nothing much to do here
}
}];
HKObserverQuery *observerQuery = [[HKObserverQuery alloc] initWithSampleType:type
predicate:nil
updateHandler:^(HKObserverQuery *query, HKObserverQueryCompletionHandler completionHandler, NSError *error) {
if (!error) {
//Create and execute a query about the sample type.
// Within the completion handler of the new query, don't forget to call completionHandler()
}
}];
[healthStore executeQuery:observerQuery];
You can find more details here:
Receiving Background Deliveries
Apps can also register to receive updates while in the background by calling the HealthKit store’s enableBackgroundDeliveryForType:frequency:withCompletion: method. This method registers your app for background notifications. HealthKit wakes your app whenever new samples of the specified type are saved to the store. Your app is called at most once per time period defined by the frequency you specified when registering.
As soon as your app launches, HealthKit calls the update handler for any observer queries that match the newly saved data. If you plan on supporting background delivery, set up all your observer queries in your app delegate’s application:didFinishLaunchingWithOptions: method. By setting up the queries in application:didFinishLaunchingWithOptions:, you ensure that the queries are instantiated and ready to use before HealthKit delivers the updates.
After your observer queries have finished processing the new data, you must call the update’s completion handler. This lets HealthKit know that you have successfully received the background delivery.
I am developing a static library that needs to do some stuff in the background, without interacting with the main thread. To give you an idea, think of just logging some user events. The library must keep doing this stuff until the user exits the app or sends it to the background (pushes the home button) - in other words it needs to keep doing stuff inside a loop.
The only interaction between the main app thread and the spawned thread is that occasionally the main app thread will put some stuff (an event object) into a queue that the spawned thread can read/consume. Other than that, the spawned thread just keeps going until the app exists or backgrounds.
Part of what the spawned thread needs to do (though not all of it) involves sending data to an HTTP server. I would have thought that it would be easy to subclass NSThread, override its main method, and just make a synchronous call to NSUrlConnection with some sort of timeout on that connection so the thread doesn't hang forever. For example, in Java/Android, we just subclass Thread, override the start() method and call a synchronous HTTP GET method (say from Apache's HttpClient class). This is very easy and works fine. But from what I have seen here and elsewhere, apparently on iOS it is much more complicated than this and I'm more than a bit confused as to what the best approach is that actually works.
So should I subclass NSThread and somehow use NSUrlConnection? It seems the asynchronous NSUrlConnection does not work inside NSThread because delegate methods don't get called but what about the synchronous method? Do I somehow need to use and configure the RunLoop and set up an autorelease pool? Or should I use an NSOperation? It seems to me that what I am trying to do is pretty common - does anyone have a working example of how to do this properly?
As I understand it, to use NSURLConnection asynchronously you need a runloop. Even if you use an NSOperation you still need a runloop.
All the examples I have seen use the Main Thread to start NSURLConnection which has a runloop. The examples using NSOperation are set up so the operation is Concurrent which tells NSOperationQueue not to provide it's own thread, they then make sure that NSURLConnection is started on the main thread, for example via a call to performSelectorOnMainThread:
Here is an example:
Pulse Engineering Blog: Concurrent Downloads using NSOperationQueues
You can also search the Apple documentation for QRunLoopOperation in the LinkedImageFetcher sample which is an example class showing some ins and outs of this kind of thing.
(Although I'm not sure I actually saw any code that example showing how to run your own runloop, again this example relies on the main thread.)
I've used the grand central dispatch (GCD) methods to achieve this. Here is an example that worked for me in a simple test app (I'm not sure if it applies in a static library, but may be worth a look). I'm using ARC.
In the example, I am kicking off some background work from my viewDidLoad method, but you can kick it off from anywhere. The key is that "dispatch_async(dispatch_get_global_queue…" runs the block in a background thread. See this answer for a good explanation of that method: https://stackoverflow.com/a/12693409/215821
Here is my viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL),
^(void) {
[self doStuffInBackground];
});
}
The doStuffInBackground method is running in the background at this point, so you can just use NSURLConnection synchronously. In my example here, the method loops making network calls until presumably some other code sets backgroundStuffShouldRun = false. A network call is made with a 10 second timeout. After the call, I'm updating a UI label just to show progress. Note that the UI update is performed with "dispatch_async(dispatch_get_main_queue()…". This runs the UI update on the UI thread, as required.
One potential issue with this background work: there isn't a way to cancel the http request itself. But, with a 10 second timeout, you'd be waiting a max of 10 seconds for the thread to abort itself after an outsider (likely some event in your UI) sets backgroundStuffShouldRun = false.
- (void)doStuffInBackground
{
while (backgroundStuffShouldRun) {
// prepare for network call...
NSURL* url = [[NSURL alloc] initWithString:#"http://maps.google.com/maps/geo"];
// set a 10 second timeout on the request
NSURLRequest* request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLCacheStorageAllowed timeoutInterval:10];
NSError* error = nil;
NSURLResponse *response = nil;
// make the request
NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// were we asked to stop the background processing?
if (!backgroundStuffShouldRun) {
return;
}
// process response...
NSString* status = #"Success";
if (error) {
if (error.code == NSURLErrorTimedOut) {
// handle timeout...
status = #"Timed out";
}
else {
// handle other errors...
status = #"Other error";
}
}
else {
// success, handle the response body
NSString *dataAsString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#", dataAsString);
}
// update the UI with our status
dispatch_async(dispatch_get_main_queue(), ^{
[statusLabel setText:[NSString stringWithFormat:#"completed network call %d, status = %#", callCount, status]];
});
callCount++;
sleep(1); // 1 second breather. not necessary, but good idea for testing
}
}
I have an NSOperationQueue that handles importing data from a web server on a loop. It accomplishes this with the following design.
NSURLConnect is wrapped in an NSOperation and added to the Queue
On successful completion of the download (using a block), the data from the request is wrapped in another NSOperation that adds the relevant data to Core Data. This operation is added to the queue.
On successful completion (using another block), (and after a specified delay) I call the method that started it all and return to step 1. Thus, i make another server call x seconds later.
This works great. I'm able to get data from the server and handle everything on the background. And because these are just NSOperations I'm able to put everything in the background, and perform multiple requests at a time. This works really well.
The ONLY problem that I currently have is that I'm unable to successfully cancel the operations once they are going.
I've tried something like the following :
- (void)flushQueue
{
self.isFlushingQueue = YES;
[self.operationQueue cancelAllOperations];
[self.operationQueue waitUntilAllOperationsAreFinished];
self.isFlushingQueue = NO;
NSLog(#"successfully flushed Queue");
}
where self.isFlushingQueue is a BOOL that I use to check before adding any new operations to the queue. This seems like it should work, but in fact it does not. Any ideas on stopping my Frankenstein creation?
Edit (Solved problem, but from a different perspective)
I'm still baffled about why exactly I was unable to cancel these operations (i'd be happy to keep trying possible solutions), but I had a moment of insight on how to solve this problem in a slightly different way. Instead of dealing at all with canceling operations, and waiting til queue is finished, I decided to just have a data structure (NSMutableDictionary) that had a list of all active connections. Something like this :
self.activeConnections = [NSMutableDictionary dictionaryWithDictionary:#{
#"UpdateContacts": #YES,
#"UpdateGroups" : #YES}];
And then before I add any operation to the queue, I simply ask if that particular call is On or Off. I've tested this, and I successfully have finite control over each individual server request that I want to be looping. To turn everything off I can just set all connections to #NO.
There are a couple downsides to this solution (Have to manually manage an additional data structure, and every operation has to start again to see if it's on or off before it terminates).
Edit -- In pursuit of a more accurate solution
I stripped out all code that isn't relevant (notice there is no error handling). I posted two methods. The first is an example of how the request NSOperation is created, and the second is the convenience method for generating the completion block.
Note the completion block generator is called by dozens of different requests similar to the first method.
- (void)updateContactsWithOptions:(NSDictionary*)options
{
//Hard coded for ease of understanding
NSString *contactsURL = #"api/url";
NSDictionary *params = #{#"sortBy" : #"LastName"};
NSMutableURLRequest *request = [self createRequestUsingURLString:contactsURL andParameters:params];
ConnectionCompleteBlock processBlock = [self blockForImportingDataToEntity:#"Contact"
usingSelector:#selector(updateContactsWithOptions:)
withOptions:options andParsingSelector:#selector(requestUsesRowsFromData:)];
BBYConnectionOperation *op = [[BBYConnectionOperation alloc] initWithURLRequest:request
andDelegate:self
andCompletionBlock:processBlock];
//This used to check using self.isFlushingQueue
if ([[self.activeConnections objectForKey:#"UpdateContacts"] isEqualToNumber:#YES]){
[self.operationQueue addOperation:op];
}
}
- (ConnectionCompleteBlock) blockForImportingDataToEntity:(NSString*)entityName usingSelector:(SEL)loopSelector withOptions:(NSDictionary*)options andParsingSelector:(SEL)parseSelector
{
return ^(BOOL success, NSData *connectionData, NSError *error){
//Pull out variables from options
BOOL doesLoop = [[options valueForKey:#"doesLoop"] boolValue];
NSTimeInterval timeInterval = [[options valueForKey:#"interval"] integerValue];
//Data processed before importing to core data
NSData *dataToImport = [self performSelector:parseSelector withObject:connectionData];
BBYImportToCoreDataOperation *importOperation = [[BBYImportToCoreDataOperation alloc] initWithData:dataToImport
andContext:self.managedObjectContext
andNameOfEntityToImport:entityName];
[importOperation setCompletionBlock:^ (BOOL success, NSError *error){
if(success){
NSLog(#"Import %#s was successful",entityName);
if(doesLoop == YES){
dispatch_async(dispatch_get_main_queue(), ^{
[self performSelector:loopSelector withObject:options afterDelay:timeInterval];
});
}
}
}];
[self.operationQueue addOperation:importOperation];
};
}
Cancellation of an NSOperation is just a request, a flag that is set in NSOperation. It's up to your NSOperation subclass to actually action that request and cancel it's work. You then need to ensure you have set the correct flags for isExecuting and isFinished etc. You will also need to do this in a KVO compliant manner. Only once these flags are set is the operation finished.
There is an example in the documentation Concurrency Programming Guide -> Configuring Operations for Concurrent Execution. Although I understand that this example may not correctly account for all multi-threaded edge cases. Another more complex example is provided in the sample code LinkedImageFetcher : QRunLoopOperation
If you think you are responding to the cancellation request correctly then you really need to post your NSOperation subclass code to examine the problem any further.
Instead of using your own flag for when it is ok to add more operations, you could try the
- (void)setSuspended:(BOOL)suspend
method on NSOperationQueue? And before adding a new operation, check if the queue is suspended with isSuspended?