Trying to replace deprecated sendSynchronousRequest calls in several command line tools I have written. The completionHandler is not getting called. Based on somewhat similar questions, I have tried using a semaphore, but still no joy. (Most questions posted concern apps, not command line tools.) Appreciate any help. Here's my code snippet:
void ebayApiCall(NSMutableURLRequest * request, NSError *error) {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSURLSessionDataTask * dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"\nCompletionHandler\n");
responseFromEbay = data; //responseFromEbay is global
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
[dataTask resume];
}
You just have to call [dataTask resume] before waiting on the semaphore.
Related
I'm writing an app which uses a web service to get some JSON data, I will need get different data from the web service across my different view controllers. So I want to create a class to handle this, with the aim to releasing this in the future.
In my class I want to make use of the AFNetworking framework using AFJSONRequestOperation to get the JSON data from the web service but this returns the data asynchronously, so isn't as simple as just returning data on the class method.
How do I make my class handle this data and pass it back to the calling class? Do I have to use delegate like I would normally when passing data back or is there another way?
+(NSDictionary*)fetchDataFromWebService:(NSString *)query{
NSURL *url = [NSURL URLWithString:query];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"Success");
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Fail");
}];
[operation start];
return ??? // I can't return anything here because AFJSONRequestOperation is completed Async
}
So should I do this, and use a delegate
+(void)fetchDataFromWebService:(NSString *)query{
NSURL *url = [NSURL URLWithString:query];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"Success");
[self.delegate didFinishFetchingJSON:(NSDictionary*)json];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Fail");
[self.delegate didFinishFetchingJSON:(NSDictionary*)json withError:(NSError*)error];
}];
[operation start];
}
Any help on the best way and best practice to create this sort of class using an async call would be very helpful.
Many Thanks in advance
When you do async calls like this definitely don't expect a traditional return setup. Your delegate idea would definitely work, you could also pass data around as a #property or something. But my preferred way is this:
- (void)postText:(NSString *)text
forUserName:(NSString *)username
ADNDictionary:(NSDictionary *)dictionary
withBlock:(void(^)(NSDictionary *response, NSError *error))block;
I declare the method with a block as a parameter. The implementation looks like this:
- (void)postText:(NSString *)text
forUserName:(NSString *)username
ADNDictionary:(NSDictionary *)dictionary
withBlock:(void(^)(NSDictionary *response, NSError *error))block
{
// Custom logic
[[KSADNAPIClient sharedAPI] postPath:#"stream/0/posts"
parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
if (block) {
block(responseObject, nil);
}
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
if (block) {
block([NSDictionary dictionary], error);
}
}];
}
As you can see once I get a response from the web service I pass the objects back into the block that it was called with. I call this method with:
[[KSADNAPIClient sharedAPI] postText:postText
forUserName:username
ADNDictionary:parameters
withBlock:^(NSDictionary *response, NSError *error)
{
if (error) {
// Handle error
} else {
// Do other stuff
}
}];
So once you call it this block doesn't do anything until it gets a response from the service. Then inside this block if you wanted to you could call something like:
[self loadInfo:response];
Yes, you'll use delegate methods.
I use a regular NSURLRequest and NSUrlConnection object and its delegate methods to make asynchronous calls and use NSJSONSerialization to parse the JSON into a NSDictionary. No third party libraries required.
NSURLRequest also has a dictionary that you can use to set any kind of data you will need to handle the request once it's returned. This way, you could potentially handle all of your requests in the same delegate methods and figure out what to do based on the request properties.
URL Loading System Programming Guide
By default NSUrlConnection's initRequest method even runs the delegate methods on the main thread, so you wouldn't have any thread safety concerns. However, you can also have them run on a separate thread by setting startImmediately to NO.
I'm not averse to using third party libraries, but this isn't a case where you need them.
As i see in all RestKit documentations, didWSRequestLoadObjects delegate function is used to handle service response.
The problem is, if I have a different requests (postObject) in my view controller i have to check response type in didWSRequestLoadObjects for each request.
Is there a way to register a function before each postObject and get each response in different function?
Which version of RestKit are you using?
On the last release it is highly encouraged to use blocks instead of a loadObjects delegate function. For example, the RKObjectManager postObject method has a success and error parameters which receives a block.
Here is an example of use:
RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:#"http://some.url"];
//Configure here your manager with response descriptors and stuff..
[manager postObject:someObject path:#"/some/path" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
//Success Response code here
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
//Error Response code here
}];
I've been trying to experiment with some code from a tutorial, however not having much success due to not getting my head around GCD.
I have an class named API.m and here is the code regarding GCD:
+ (API *) sharedInstance
{
static API *sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
sharedInstance = [[self alloc] initWithBaseURL:[NSURL URLWithString:APIHost]];
});
return sharedInstance;
}
-(void)commandWithParams:(NSMutableDictionary*)params
onCompletion:(JSONResponseBlock)completionBlock
{
NSMutableURLRequest *apiRequest = [self multipartFormRequestWithMethod:#"POST"
path:APIPath
parameters:params
constructingBodyWithBlock: ^(id <AFMultipartFormData>formData) {
//TODO: attach file if needed
}];
AFJSONRequestOperation* operation = [[AFJSONRequestOperation alloc] initWithRequest: apiRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//success!
completionBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//failure :(
completionBlock([NSDictionary dictionaryWithObject:[error localizedDescription] forKey:#"error"]);
}];
[operation start];
}
I make a simple test by implementing a button and getting an NSArray to print it's content to the output window:
- (IBAction)test:(id)sender {
NSMutableDictionary* params =[NSMutableDictionary dictionaryWithObjectsAndKeys:
#"pending", #"command",
[[[API sharedInstance] user] objectForKey:#"UserID"] , #"userID",
nil];
[[API sharedInstance] commandWithParams:params
onCompletion:^(NSDictionary *json) {
//result returned
if ([json objectForKey:#"error"]==nil) {
// Simple example
[self.users addObject:#"1"];
} else {
//error
[UIAlertView title:#"Error" withMessage:[json objectForKey:#"error"]];
}
}];
NSLog(#"%#", self.users);
}
Now when I first click the button an empty NSArray is printed to the output window, but when I press it again it print's "1". It's clear that the program is reaching NSLog before the completion block has time to fully execute. Could someone please help me modify the code so that I have the option to have the NSLog execute after the completion block has finished?
Not sure as to what you are trying to accomplish, but if the goal is to just have NSLog execute after the completion block, you can move the NSLog statement after
[self.users addObject:#"1"];
If you have some code which you want to execute after adding it to the array, you can have
[self methodName]; in the completion block and it will get called there.
Completion block, is the code which is run after execution of the code which you wanted run. The code which you wanted run, will happen asynchronously and on another thread. After that code is run, the completion block code will get executed.
I need to download a queue of images.
I created my operations first, then add them with the "enqueue" method of AFNetworking.
I have 2 problems :
1) I didn't have the progress bar working for the queue (and I have it working with a custom operation queue)
2) I didn't find the solution to stop the queue when I want
I created first operations to batch and add theme in a array:
while ((dict = [enumerator nextObject]))
{
NSMutableURLRequest *request = [[MyHTTPClient sharedClient] requestWithMethod:#"GET" path:#"ws/webapp/services/pull_image" parameters:dict];
AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request
imageProcessingBlock:nil cacheName:nil
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image)
{
NSLog(#"image : %#", [image description]);
// process images
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error)
{
// manage errors
}];
[operations addObject:operation];
}
Then, I enqueue the operations:
[[MyHTTPClient sharedClient] enqueueBatchOfHTTPRequestOperations:operations
progressBlock:^(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations)
{
float percentDone = ((float)((int)numberOfCompletedOperations) / (float)((int)totalNumberOfOperations));
[delegate syncServicesController:self updateProgressView:percentDone];
}
completionBlock:^(NSArray *operations)
{
//
}];
So, the progress download didn't work.
But I can see the progress of numberOfCompletedOperations... ? 1,2,3,4,5... Does I need to force the refresh of the progress view in the main thread ?
And when I tried to stop the network tasks:
- (void)cancelAllRequests
{
[[MyHTTPClient sharedClient] cancelAllHTTPOperationsWithMethod:#"GET" path:#"ws/webapp/services/pull_image"];
}
I don't understand how to stop the queue of requests... This seems that works but I have this error : -[NSBlockOperation request]: unrecognized selector sent to instance 0x16f54c70
These were actually just fixed in the last day or two :)
Go ahead and update to the latest version of master, which includes the following:
cc2115e469: Progress blocks now dispatch to main by default, just like all of the other completion blocks in AFNetworking. This should fix any issues around the UI not updating there.
cac44aeb34: Fixes that problem with NSBlockOperation being sent request. There was an incorrect assumption baked into cancelAllHTTPOperationsWithMethod: that all operations were AFHTTPRequestOperation. The only downside is that it will not handle your batched operations. For that, you can always iterate through httpClient.operationQueue.operations and pick out the one you want.
I'm trying to queue up some TWRequest calls using NSInvocationOperation. It seems to add the method calls in the correct order, but the doSomething: methods get called at the same time pretty much, ie run concurrently, rather than one after the other, which is what I want to achieve.
In addition, the complete in the wrong order, which suggests it's not running one after the other...
- (void)prepare {
if(!self.queue){
self.queue = [[NSOperationQueue alloc] init];
[self.queue setMaxConcurrentOperationCount:1];
}
for(NSString *text in calls){
NSLog(#"Adding to Queue... %#", text);
NSInvocationOperation *indexOperation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(doSomething:) object:text];
[self.queue addOperation:indexOperation];
}
}
- (void)doSomething:(NSString*)someText {
TWRequest *request = [[TWRequest alloc] initWithURL:[NSURL URLWithString:#"http://something.com"] parameters:nil requestMethod:TWRequestMethodGET];
NSLog(#"About to Perform Request... %#", someText);
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
dispatch_sync(dispatch_get_main_queue(), ^{
// works fine
NSLog(#"Network Finished... %#", someText);
});
}];
}
In the log I see this:
2011-12-30 18:34:34.553 app[32745:10703] Adding to Queue... 1
2011-12-30 18:34:34.555 app[32745:10703] Adding to Queue... 2
2011-12-30 18:34:34.556 app[32745:10703] Adding to Queue... 3
2011-12-30 18:34:34.557 app[32745:13e03] About to Perform Request... 1
2011-12-30 18:34:34.560 app[32745:13e03] About to Perform Request... 2
2011-12-30 18:34:34.563 app[32745:13e03] About to Perform Request... 3
2011-12-30 18:34:35.303 app[32745:10703] Network finished... 3
2011-12-30 18:34:35.454 app[32745:10703] Network finished... 2
2011-12-30 18:34:35.601 app[32745:10703] Network finished... 1
I'm expecting to see (2) to Perform Request after (1) has finished etc... Any pointers?
The operation queue is working fine. As #Joe said in the comment, performRequestWithHandler: starts an asynchronous connection and returns immediately. You can see this by adding an NSLog to the end of doSomething as follows:
- (void)doSomething:(NSString*)someText {
TWRequest *request = [[TWRequest alloc] initWithURL:[NSURL URLWithString:#"http://something.com"] parameters:nil requestMethod:TWRequestMethodGET];
NSLog(#"About to Perform Request... %#", someText);
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
dispatch_sync(dispatch_get_main_queue(), ^{
// works fine
NSLog(#"Network Finished... %#", someText);
});
}];
NSLog(#"doSomething Finished");
}
To have each request happen serially you need to either make the request synchronous within the operation (use the signedRequest method and a synchronous NSURLConnection) or don't use an operation queue and invoke the next request in the completion handler of the current request. Keep in mind that if you use an operation queue the order in which operations are performed is not based on the order they are added. You might consider using GCD directly with a serial dispatch queue.