Trying to make rest api calls from an iOS app using this method http://spring.io/guides/gs/consuming-rest-ios/
Need to make 3 api calls one after another and need to utilize part of the json result in the subsequent api call. How should I proceed?
iOS app is been developed using Objective-C
Setup a background thread like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
<#code#>
});
And execute your API calls synchronously within it. It might look something like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
id dataOne = [self apiCallOne];
id dataTwo = [self apiCallTwoWithDataOne:dataOne];
id dataThree = [self apiCallThreeWithDataTwo:dataTwo];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Completion with data: %#", dataThree);
});
});
Make sure to use [NSURLConnection sendSynchronousRequest: ...]; as opposed to [NSURLConnection sendAsynchronousRequest: ...];
Related
I have four api calls to make. They should be in following order:
apiSyncDataToCloud;
apiSyncImagesToServer;
apiDeleteDataFromCloud;
apiSyncDataFromCloudInBackground;
Each one of them is to be called irrespective of the fact that previous one finishes successfully or fails.
Also, each one of them have success and failure completion blocks.
In success completion block database is updated.
All this process has to be performed in background and has to be done a no of times.
Api calls are of course performed in background but once a call completes database update is performed on main thread thereby freezing the app.
So, I went with several solutions:
Tried following code:
NSOperationQueue *queue = [NSOperationQueue new];
queue.maxConcurrentOperationCount = 1;
[queue addOperationWithBlock:^{
[self apiSyncDataToCloud];
}];
[queue addOperationWithBlock:^{
[self apiSyncImages];
}];
[queue addOperationWithBlock:^{
[self apiDeleteDataFromCloud];
}];
[queue addOperationWithBlock:^{
[self apiSyncDataFromCloudInBackground];
}];
But this only guarantees that api method calls will be performed in order. But their result follows no specific order. That is, method calls will be in the order specified but success block of apiSyncImagesToServer may be called before success block of apiSyncDataToCloud.
Then I went with following solution:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self apiSyncDataToCloud];
});
and in the success and failure blocks of apiSyncDataToCloud I have called apiSyncImagesToServer. This too did'nt work.
Now I am simply going with my last solution. I am just calling apiSyncDataToCloud.
In success completion block this method first updates the database and then calls other api.
In failure completion block this method simply makes the api call without updating the database.
For example-
structure of apiSyncDataToCloud is as follows:
-(void)apiSyncDataToCloud{
NSLog(#"method 1");
NSMutableDictionary *dicDataToBeSynced = [NSMutableDictionary dictionary];
dicDataToBeSynced = [self getDataToBeSynced];
if (dicDataToBeSynced.count!=0) {
if ([[StaticHelper sharedObject] isInternetConnected]) {
[[ApiHandler sharedObject] postRequestWithJsonString:API_SYNC_DATA_TO_CLOUD andHeader:[UserDefaults objectForKey:kAuthToken] forHeaderField:kAccessToken andParameters:dicDataToBeSynced WithSuccessBlock:^(NSURLResponse *response, id resultObject, NSError *error) {
NSLog(#"Data synced successfully to server");
[self updateColumnZSYNC_FLAGForAllTables];//updating db
[self apiSyncImagesToServer];//api call
} andFailureBlock:^(NSURLResponse *task, id resultObject, NSError *error) {
NSLog(#"Data syncing to cloud FAILED");
[self apiSyncImagesToServer];//simply make api call without updating db
}];
}
}else{
[self apiSyncImagesToServer];make api call even if no data to be synced found
}
}
Similary, inside apiSyncImagesToServer I am calling apiDeleteDataFromCloud.....
As a result my problem remained as it is. App freezes when it comes to success block updating db, downloading images...all operations being performed on main thread.
Plz let me know a cleaner and better solution.
You can create your own custom queue and call request one by one.
i.e.
dispatch_queue_t myQueue;//declare own queue
if (!myQueue) {//check if queue not exists
myQueue = dispatch_queue_create("com.queue1", NULL); //create queue
}
dispatch_async(myQueue, ^{[self YOUR_METHOD_NAME];});//call your method in queue block
If you want update some UI after receiving data then update UI on main Thread.
1) Better to use AFNetworking for this kind of situations. Because AFNetworking provides better way to handle Main & Background Threads.
AFNetworking supports success and failure blocks so you can do one by one WS Api calls from success and failure of previous WS Api call. So during this time period show progress HUD. Success of last API then update DB and hide progress HUD.
2) If you need to use NSOperationQueue and NSInvocationOperation
and follow this link. https://www.raywenderlich.com/76341/use-nsoperation-nsoperationqueue-swift
Api calls are of course performed in background but once a call
completes database update is performed on main thread thereby freezing
the app.
Then why not perform it in a separate queue?
Try using
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//your code
});
to perform time-consuming tasks and
dispatch_async(dispatch_get_main_queue(), ^{
//your code
});
to only update UI.
I am using objective-C to write an app which needs to dispatch 100 web request and the response will be handled in the call back. My question is, how can I execute web req0, wait for call back, then execute web req1 and so on?
Thanks for any tips and help.
NSURL *imageURL = [[contact photoLink] URL];
GDataServiceGoogleContact *service = [self contactService];
// requestForURL:ETag:httpMethod: sets the user agent header of the
// request and, when using ClientLogin, adds the authorization header
NSMutableURLRequest *request = [service requestForURL:imageURL
ETag: nil
httpMethod:nil];
[request setValue:#"image/*" forHTTPHeaderField:#"Accept"];
GTMHTTPFetcher *fetcher = [GTMHTTPFetcher fetcherWithRequest:request];
fetcher.retryEnabled = YES;
fetcher.maxRetryInterval = 0.3;
fetcher.minRetryInterval = 0.3;
[fetcher setAuthorizer:[service authorizer]];
[fetcher beginFetchWithDelegate:self
didFinishSelector:#selector(imageFetcher:finishedWithData:error:)];
}
- (void)imageFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data error:(NSError *)error {
if (error == nil) {
// got the data; display it in the image view. Because this is sample
// code, we won't be rigorous about verifying that the selected contact hasn't
// changed between when the fetch began and now.
// NSImage *image = [[[NSImage alloc] initWithData:data] autorelease];
// [mContactImageView setImage:image];
NSLog(#"successfully fetched the data");
} else {
NSLog(#"imageFetcher:%# failedWithError:%#", fetcher, error);
}
}
You can't simply call this code in a loop as GTMHTTPFetcher works asynchronously so the loop, as you see, will iterate and start all instances without any delay.
A simple option is to put all of the contacts into a mutable array, take the first contact from the array (remove it from the array) and start the first fetcher. Then, in the finishedWithData callback, check if the array contains anything, if it does remove the first item and start a fetch with it. In this way the fetches will run serially one after the other.
A better but more complex solution would be to create an asynchronous NSOperation (there are various guides on the web) which starts a fetch and waits for the callback before completing. The benefit of this approach is that you can create all of your operations and add them to an operation queue, then you can set the max concurrent count and run the queue - so you can run multiple fetch instances at the same time. You can also suspend the queue or cancel the operations if you need to.
I wanted to translate this in swift :
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL: googleRequestURL];
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:data waitUntilDone:YES];
});
It's for using google places api.
I wondering about using a simple NSURLSession request but it seems that the dataWithContentsOfURL do the job of an NSURLSession request ?
Someone ?
dataWithContentsOfURL is discouraged. You should use NSURLSession for asynchronous downloads, or if you prefer the simpler NSURLConnection.
The delegate callbacks tell the main thread when the download is finished - so no need to engage with Great Central Dispatch APIs.
Mundi is right that the better tool here is NSURLSession. But your code can work; just need to use GCD correctly, and deal with the fact that it might fail:
dispatch_async(kBgQueue) {
if let data = NSData.dataWithContentsOfURL(googleRequestURL) {
dispatch_sync(dispatch_get_main_queue()) { self.fetchedData(data) }
} else {
// Here's the problem with dataWithContentsOfURL. You had an error, but you
// don't know what it was. I guess you'll do something here...
}
}
I am needing to perform a very simple background check for my iOS app. It needs to just make one call to my web server and check the number it retrieves against something in my app. Is it possible to do that kind of background check? If so what can I do to put it together?
EDIT
To clarify what I mean by background: I am meaning background on the phone. When the app is not present. Is it possible to do this request in the background? Obviously with the app not being completely closed out from multitasking.
This sounds like the perfect sort of thing for NSOperationQueue.
http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues
You can write an operation and then put it on the queue when you need it.
Alternatively, and more simply, you can just do a really simple asynchronous call.
+ (NSArray *) myGetRequest: (NSURL *) url{
NSArray *json = [[NSArray alloc] init];
NSData* data = [NSData dataWithContentsOfURL:
url];
NSError *error;
if (data)
json = [[NSArray alloc] initWithArray:[NSJSONSerialization
JSONObjectWithData:data
options:kNilOptions
error:&error]];
if (error)
NSLog(#"%#", error)
return json;
}
and then put it in a simple dispatch block...
dispatch_queue_t downloadQueueA = dispatch_queue_create("updater", NULL);
dispatch_async(downloadQueueA, ^{
// stuff done here will happen in the background
NSArray * arrayOfData = [self myGetRequest: myURL];
// could be array... dictionary... whatever, you control this by returning the type of data model you want from the server formatted as JSON data
NSString * stringValue = arrayOfData[index];
dispatch_async(dispatch_get_main_queue(), ^{
// perform checking here and do whatever updating you need to do based on the data
});
});
There are many way to check your server and retrieve data.
Here my suggestion:
Create the file containing your data on the server (e.g. Data.txt)
Use NSURLRequest to create a request to Data.txt
Use connectionDidFinishLoading to get data from Data.txt
Put data from Data.txt in a NSArray
Work/compare the array and do your logic
If your server is fast and you have to get just one number, you can do it in the main tread, otherwise use:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
// your request here
});
to work in a different tread as requested.
And remember to check if internet connection and your server are available with something like Reachability and manage connection error with NSURLRequest delegate
You should be able to do that using Grand Central Dispatch: https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html
Take a look at this tutorial Multithreading and Grand Central Dispatch on iOS.
http://www.raywenderlich.com/4295/multithreading-and-grand-central-dispatch-on-ios-for-beginners-tutorial
This question already has answers here:
loading images from a background thread using blocks
(3 answers)
Closed 8 years ago.
I'm trying to download an image from a website and save it as a UIImage but if the user has low connection this can take forever... how can I download it in the background so the user can keep using the app in the meantime?
here is the code:
theIcon.image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://myWebsite.com/Icon.png"]]];
Use GCD.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// do my background code
dispatch_async(dispatch_get_main_queue(), ^{
// do handling on main thread when done!
});
});
Use AFNetworking.
[imageView setImageWithURL:
[NSURL URLWithString:#"http://i.imgur.com/r4uwx.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder-avatar"]];
You can perform the selector in background thread while the main foreground thread runs.
[self performSelectorInBackground:#selector(downloadFile) withObject:nil];
- (void) downloadFile {
//download file
//you can show UIAlertView when done
}
In your - (void) downloadFile you can download this big file.
and have an activity indicator show (or not). You can have the activity Indicator become not hidden or hidden and have it startAnimating and stopAnimating will make it spin and stop. This can be referenced from the foreground and background processes.
The quick and dirty way:
NSMutableRequest* request = ... ;
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse* response, NSData* data, NSError* error) {
if (!error) {
// do something with the response data.
}
}];
This approach is sufficient for "a prove of concept", toy programs with simplistic insecure connections, Apple samples, and for hobbyists learning iOS for fun, and for samples which demonstrate anti-patterns ("How you should do it, not!").
If you want a solid approach you need to use NSURLConnection in asynchronous mode and implement the delegates - or use a third party library. ;)