I am using AFNetworking kit to develop an iOS app.
When the app is launching, it requests an access token from the server.
The code is simple as:
__block int status = 0;
[manager GET:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject)
{
...fetch access token from response json string
status = 1; //break the loop
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
...networking error
}
and also, to make sure the other requests to the server own the access token, I have put a loop to block the thread until the server responses.
while (status == 0)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate date]];
}
This worked before, until I imported a third party notification push library. And the async method:
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
is continuing print deviceToken request in console and the access token request never responds anything, even the server actually provides the response.
I have been stuck for day, can someone helps me?
UPDATE I have tried to comment the device token stuff, the AFNetworking request still doesn't work, no success no failure and no timeout:
UPDATE2 Clarify my question. AFHTTPRequestOperationManager have sent a GET request to the server, and server responses. But the AFHTTPRequestOperationManager doesn't receive it, and no success no failure callback as well.
A couple of thoughts:
If you're going to use this pattern:
you really should set status to 1 in the error block, too; and
Apple uses distantFuture in its examples, not date:
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
You might have something else blocking the main thread. Try putting a NSLog inside the while loop and see if you see that loop running. If not, then identify where you're blocking the main thread.
Needless to say, this overall pattern is inefficient. I'd suggest employing asynchronous patterns, such as adopting completion handlers in your own code:
- (void)performSomeURL:(NSString *)url completionHandler:(void (^)(NSDictionary *response, NSError *error))completionHandler{
NSDictionary *parameters = ...;
[manager GET:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
completionHandler(responseObject, nil);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
completionHandler(nil, error);
}];
}
which could be called like:
[self performSomeURL:url completionHandler:^(NSDictionary *response, NSError *error) {
// use response and error here
}];
// but not here
If you always adopt asynchronous patterns like this in all of your code, never doing anything that blocks the main thread, then not only will your code be more efficient, but you'll never have to fear about deadlocking on the main thread.
Related
I have a simple app which try to login users.
User insert username and password
I call a method in another class ->
if ([myBankLogger checkUserLogin:self.memberNumber.text :self.accessCode.text])
{
//Check if user credential is correct
NSLog(#"Corrct");
}
else
{
NSLog(#"Not correct");
}
In checkUserLogin I send a http request to server and it takes a minute to get respond:
-(bool) checkUserLogin :(NSString*)username :(NSString*)password
{
__block bool tmp= false;
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager POST:string parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSLog(#"JSON: %#", responseObject);
if (responseObject[#"secret_token"])
{
NSLog(#"HERE");
tmp = true;
}
} failure:^(NSURLSessionTask *operation, NSError *error) {
NSLog(#"Error: %#", error);
tmp = false;
}];
return tmp;
}
As it is normal compiler dose not wait until it finish processing. It returns false. I researched and found out I must use this:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
// Perform async operation
// Call your method/function here
// Example:
// NSString *result = [anObject calculateSomething];
dispatch_sync(dispatch_get_main_queue(), ^{
// Update UI
// Example:
// self.myLabel.text = result;
});
});
But I am not sure where I should put my code? Appreciate any suggestion or easier solution.
If you perform a synchronous operation on the main thread, this will block the app UI for the entire duration of that sync operation. If it takes too much, the OS will kill the application execution. With an async approach using the dispatch method, you can solve the UI blocking problem:
[self.myRingLoading startAnimating]; // show a ring loading while the following long operation will take (here you are in the main thread scope)
// this dispatch block will move all the actions in an async thread, avoiding any UI blocking issue
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
// Perform here your UI blocking operation
// sample blocking ui operation
NSData* httpDataResult = [NSData dataWithContentsOfURL:#"http://api.yoursite.com/xyz"];
NSString* httpStringResult = [[NSString alloc] initWithData:httpDataResult encoding:NSUTF8StringEncoding];
// this dispatch block will send all action execution to the main queue, commonly the main thread
dispatch_async(dispatch_get_main_queue(), ^(void){
// Perform your UI operation (eg: printing the http response received by the server
[self.myUILabel setText:httpStringResult];
[self.myRingLoading stopAnimating]; // hide the ring loading since the long operation is finished
});
});
There are other native solutions for obtaining the result, for example using the NSOperation and NSOperationQueue. If you use AFNetworking, you can take advantage of several good implementations, for example AFHTTPRequestOperation:
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// Perform your UI actions for the Success case
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Perform your UI actions for the Failed case
}
];
// run the http operation process
[operation start];
Hope it helps
I have an app that needs to use AFNetworking and I'm having an issue with the operations being asynchronous. I know there are a lot of similar questions here, but I have not found the answer to my issue yet.
The app calls a login method in - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
That method looks like this:
- (void)loginWithUsername:(NSString *)username password:(NSString *)password {
url = [NSURL URLWithString:#"Users/Login/" relativeToURL:baseURL];
NSDictionary *parameters = #{UsernameParameter: username, PasswordParameter: password};
[requestManager GET:url.absoluteString parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
user = [[User alloc] initWithServerResponse:responseObject];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(error.description);
}];
}
The response contains a couple of authorization tokens that are necessary for all other calls to the remote API. The problem is that the other operations are being executed before the login response is received and the success or failure blocks are executed. As a result, the app is crashing (or does nothing if I check for a nil user) when it tries to make another call to the API. What is the best way to ensure that the login response is received before proceeding?
Thank you.
do the another calling of remote api after user's login call (in the block).
e.g.
NSDictionary *parameters = #{UsernameParameter: username, PasswordParameter: password};
[requestManager GET:url.absoluteString parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
user = [[User alloc] initWithServerResponse:responseObject];
///insert another remote api here.
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(error.description);
///warnning for error.
}];
I've got subclass of AFHTTPClient
The main idea is that i call all API through my singleton of AFHTTPClient subclass, and all requests goes through 1 points for error handling and HUD displaying.
This is entry point for every API calls:
-(void) makeRequestWithPath:(NSString*) path andParams:(NSDictionary*) params
success:(void (^)( id JSON, AFHTTPRequestOperation *operation)) success
failure:(void (^)( NSError *error)) failure
And i've got many methods for API calls something like that:
-(void) getListMainTreeWithSuccess:(void (^)( id JSON, AFHTTPRequestOperation *operation)) success
failure:(void (^)( NSError *error)) failure
{
[self makeRequestWithPath:#"objects/selectlist" andParams:nil success:^(id JSON, AFHTTPRequestOperation *operation) {
success(JSON,operation);
} failure:^(NSError *error) {
failure(error);
}];
}
This works just fine for my needs. But i faced problem that i need to make serial request in loop through my AFHTTPClient subclass and make some action when all of them are finished , I found method
-(void)enqueueBatchOfHTTPRequestOperationsWithRequests:(NSArray *)urlRequests
progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock
completionBlock:(void (^)(NSArray *operations))completionBlock
which should solve my issue, but the problem is that i call all methods through AFHTTPClient and it's methods getPath: and postPath: and previous way forces me to rewrite everything and makes my subclass completely useless, because I need to add there NSArray of AFHTTPRequestoperation, which is not possible to construct or extract from my subclass and my methods. Previously i tried to use __block 's to synchronise requests with semaphore and something else but i failed to get what i need, please help me!
UPDATE:
It seems that it is not possible to even use enqueueBatchOfHTTPRequestOperations method (even with rewriting all my code) because this method needs array of http request operations, but it's not possible to construct POST request with them.
I solved this with an increment/decrement pending download system and tied the HUD to that.
[networkStatus beginNetworkActivity];
[client someRESTActionWithCompletion:^(id object, NSError *error) {
[networkStatus endNetworkActivity];
if (error) {
// Handle the error ...
}
if (![networkStatus hasNetworkActivity]) {
// All downloads have finished
}
}];
I keep the network status object separate which from the AFHTTPClient subclass, but it can be built into the client if that's what you want.
Network status keeps an internal counter. -beginNetworkActivity increments the counter, if the counter was 0, then it displays a HUD. -endNetworkActivity decrements the counter, if the counter becomes 0, then it dismisses the HUD. -hasNetworkActivity returns YES if the counter greater than 0.
Other Notes: I combine the success and failed callbacks into a single completion callback. I keep the network status logic separate from the client because sometime I'll use a singleton network status object, sometimes I'll use a created instance, sometimes I won't use one at all. It all depends on the needs to the higher level logic.
Again, as #MikePollard said, create AFHTTPRequestOperation using
[AFHHTPClient HTTPRequestOperationWithRequest:success:failure:]
For this method create NSURLRequest using (or use another one, pick which one is suitable for you). Here you can also specify, which method to use POST, GET or any other.
[AFHTTPClient requestWithMethod:
path:
parameters:]
After that save all operation to an NSArray, and schedule them using:
[AFHTTPClient enqueueBatchOfHTTPRequestOperationsWithRequests:
progressBlock:
completionBlock:]
Code example:
NSMutableArray *ops = [NSMutableArray new];
NSMutableURLRequest *request1 = [[AFHTTPClient sharedClient] requestWithMethod:#"GET"
path:#"MyEndpoint"
parameters:#{#"key1": #"value"}];
AFHTTPRequestOperation *op1 = [[AFHTTPClient sharedClient] HTTPRequestOperationWithRequest:request1
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Success!");
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure!");
}];
[ops addObject:op1];
NSMutableURLRequest *request2 = [[AFHTTPClient sharedClient] requestWithMethod:#"POST"
path:#"MyAnotherEndpoint"
parameters:#{#"key2": #(104)}];
AFHTTPRequestOperation *op2 = [[AFHTTPClient sharedClient] HTTPRequestOperationWithRequest:request2
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Success!");
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure!");
}];
[ops addObject:op2];
[[AFHTTPClient sharedClient] enqueueBatchOfHTTPRequestOperationsWithRequests:ops
progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
NSLog(#"numberOfFinishedOperations: %d totalNumberOfOperations %d",
numberOfFinishedOperations,
totalNumberOfOperations);
}
completionBlock:^(NSArray *operations) {
NSLog(#"All operation compelted!");
}];
- (BOOL)do_a_Restkit_request_and_return_a_boolean
{
[manager postObject:nil path:#"/mypath" parameters:#{#"password":password} success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
myResult = [mappingResult firstObject] == 5;
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
}];
return myResult;
}
Hello I would like to make a RestKit call like the above Synchronous so as to return myResult after the call of the Success block.
You can use an approach like this:
NSMutableURLRequest *request = // create a request…
RKObjectRequestOperation *operation = [manager objectRequestOperationWithRequest:request success:nil failure:nil];
[operation start];
[operation waitUntilFinished];
BOOL myResult = NO;
if (!operation.error) {
myResult = [operation.mappingResult firstObject] == 5;
}
return myResult;
Notes:
The completion blocks are called after the operation is finished, so if you provide success/failure blocks, they won't be called until after the method returns
(This is why I suggest you pass nil into the completion blocks.)
waitUntilFinished will block whatever thread you're on, so make sure it's not the main thread.
If you need help creating request, see Creating Request Objects in the RKObjectManager Class Reference.
Again, if you can rewrite your code to work asynchronously, that will probably be better than this solution.
You need to either embrace the asynchronous nature of network communication and background processing of the response, or use a different API / technology specifically for making synchronous requests. RestKit and AFNetworking are based more around the former.
Embracing asynchronous is the best option...
That said, you could use RestKit to create the NSURLRequest, then use NSURLConnection to synchronously download the response, then use RKMapperOperation to perform the mapping (and wait for completion with addOperations:waitUntilFinished:).
So I'm rewriting an app for iOS 7 with AFNetworking 2.0 and I'm running into the issue of sending a batch of requests at once and tracking their progress. In the old AFNetworking there was the enqueueBatchOfHTTPRequestOperations:progressBlock:completionBlock: method on AFHTTPClient, this is clearly refactored out and I'm a bit confused on how to enqueue multiple requests.
I have created a subclass of AFHTTPSessionManager and I'm using the POST:... and GET:... methods to communicate with the server. But I can't find anything in the code and/or docs to enqueue multiple requests at once like with the old AFHTTPClient.
The only thing I can find is the undocumented batchOfRequestOperations:progressBlock:completionBlock: method on AFURLConnectionOperation, but that looks like the iOS 6 way of doing this.
Clearly I'm missing something in the new NSURLSession concept that I should use to batch requests or looking over a new AFNetworking feature. Hope someone can help me on the right track here!
tl;dr: How can I send a batch of requests with my AFHTTPSessionManager subclass?
Thanks Sendoa for the link to the GitHub issue where Mattt explains why this functionality is not working anymore. There is a clear reason why this isn't possible with the new NSURLSession structure; Tasks just aren't operations, so the old way of using dependencies or batches of operations won't work.
I've created this solution using a dispatch_group that makes it possible to batch requests using NSURLSession, here is the (pseudo-)code:
// Create a dispatch group
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i < 10; i++) {
// Enter the group for each request we create
dispatch_group_enter(group);
// Fire the request
[self GET:#"endpoint.json"
parameters:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
// Leave the group as soon as the request succeeded
dispatch_group_leave(group);
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
// Leave the group as soon as the request failed
dispatch_group_leave(group);
}];
}
// Here we wait for all the requests to finish
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// Do whatever you need to do when all requests are finished
});
I want to look write something that makes this easier to do and discuss with Matt if this is something (when implemented nicely) that could be merged into AFNetworking. In my opinion it would be great to do something like this with the library itself. But I have to check when I have some spare time for that.
Just updating the thread... I had the same problem and after some researches I found some good solutions, but I decided to stick with this one:
I am using the project called Bolts. So, for the same sample above posted by #Mac_Cain13, it would be:
[[BFTask taskWithResult:nil] continueWithBlock:^id(BFTask *task) {
BFTask *task = [BFTask taskWithResult:nil];
for (int i = 0; i < 10; i++) {
task = [task continueWithBlock:^id(BFTask *task) {
return [self executeEndPointAsync];
}];
}
return task;
}] continueWithBlock:^id(BFTask *task) {
// Everything was executed.
return nil;
}];;
- (BFTask *) executeEndPointAsync {
BFTaskCompletionSource *task = [BFTaskCompletionSource taskCompletionSource];
[self GET:#"endpoint.json" parameters:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
[task setResult:responseObject];
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
[task setError:error];
}];
}];
return task.task;
}
Basically, it's stacking all of the tasks, waiting and unwrapping until there is no more tasks, and after everything is completed the last completion block is executed.
Another project that does the same thing is RXPromise, but for me the code in Bolts was more clear.
For request which can be post or get, you can use AFNetworking 2.0 for batch operation as firstly you need to create operation like this:
//Request 1
NSString *strURL = [NSString stringWithFormat:#"your url here"];
NSLog(#"scheduleurl : %#",strURL);
NSDictionary *dictParameters = your parameters here
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] requestWithMethod:#"POST" URLString:strURL parameters:dictParameters error: nil];
AFHTTPRequestOperation *operationOne = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operationOne = [AFHTTPResponseSerializer serializer];
[operationOne setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
//do something on completion
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"%#",[error description]);
}];
//Request 2
NSString *strURL1 = [NSString stringWithFormat:#"your url here"];
NSLog(#"scheduleurl : %#",strURL);
NSDictionary *dictParameters1 = your parameters here
NSMutableURLRequest *request1 = [[AFHTTPRequestSerializer serializer] requestWithMethod:#"POST" URLString:strURL1 parameters:dictParameters1 error: nil];
AFHTTPRequestOperation *operationTwo = [[AFHTTPRequestOperation alloc] initWithRequest:request1];
operationTwo = [AFHTTPResponseSerializer serializer];
[operationTwo setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
//do something on completion
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"%#",[error description]);
}];
//Request more here if any
Now perform batch operation like this :
//Batch operation
//Add all operation here
NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:#[operationOne,operationTwo] progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations)
{
NSLog(#"%i of %i complete",numberOfFinishedOperations,totalNumberOfOperations);
//set progress here
yourProgressView.progress = (float)numberOfFinishedOperations/(float)totalNumberOfOperations;
} completionBlock:^(NSArray *operations)
{
NSLog(#"All operations in batch complete");
}];
[[NSOperationQueue mainQueue] addOperations:operations waitUntilFinished:NO];
On AFNetworking 2.0, AFHTTPClient has been split on AFHTTPRequestOperationManager and AFHTTPSessionManager, so probably you could start with the first, which has operationQueue property.
Currently, NSURLSession tasks are not suitable for the same kind of patterns request operations use. See the answer from Mattt Thompson here regarding this issue.
Direct answer: if you need dependencies or batches, you'll still need to use request operations.