I am using the AFNetworking to connect to API in my code.
Now I have general things on my view controllers like switches and picker views which will determine what is sent to the API.
So I will need to call the AFNetworking block like this :
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:returncompletedURL];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
operation.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"text/html"];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
and at the end I use [operation start] to run the block, quite easy!
but I need this in some sort of a method/function so that I can call it and pass arguments over to it as and when a switch is changed or a label is changed. Instead of writing the same block over and over again i want it as a function.
I would use [operation start] in the function to check if a switch is on/off but it does not see it within the function.
If i wrap a method around the AFNetworking block would that bad and I cannot use the RETURN statement within a block.
Generally advice is needed if i have switches and labels and if the user changes any of this then the block needs to be called straight the way to amend the API call.
thanks
I could do something like this, create a method that can return for you value:
Method that you could insert your request.
-(void)callRequestWithParameter:(id)parameter returnState:(void(^)(id response))response{
// here make request
// use parameter to send what you want on request
if (requestSucess){
response(data for return our status);
}
};
//Method for handle request and answer
-(void)requestData:(id)dataParameter {
[ClassName callRequestWithParameter:#(2) returnState: (id response)^{
if(response){
// call some update method like
[self updateItensWithData:data];
}
}
};`
Related
I am working on iOS App, and I am using AFNetworking for interacting with server API.
My issue is I want to send call and don't want to restrict user until response get from server, so issue is crash. When user move back to that particular screen lets say I have listing screen where I am getting data which is taking 6-7 seconds and meanwhile user move back to previous screen and when data come from API and call back that delete to listing screen but user move backed to that screen then App crashes
Here below is code for fetching data call.
+ (void) getRequestForDocumentListing:(NSDictionary *)headerParams urlQuery: (NSString*)action parameters:(NSDictionary*)params
onComplete:(void (^)(id json, id code))successBlock
onError:(void (^)(id error, id code))errorBlock
{
NSString *authorizationValue = [self setAuthorizationValue:action];
NSString *selectedLanguage = [ApplicationBaseViewController getDataFromDefaults:#"GLOBALLOCALE"];
NSString *language = selectedLanguage;
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
//set headers values
[manager.requestSerializer setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[manager.requestSerializer setValue:language forHTTPHeaderField:#"Accept-Language"];
[manager.requestSerializer setValue:authorizationValue forHTTPHeaderField:#"authorization"];
[manager.requestSerializer setValue:#"x-folder" forHTTPHeaderField:#"inbox"];
[manager GET:action parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"document listing success");
NSInteger statusCode = [operation.response statusCode];
NSNumber *statusObject = [NSNumber numberWithInteger:statusCode];
successBlock(responseObject, statusObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSInteger statusCode = [operation.response statusCode];
NSNumber *statusObject = [NSNumber numberWithInteger:statusCode];
id responseObject = operation.responseData;
id json = nil;
id errorMessage = nil;
if (responseObject) {
json = [NSJSONSerialization JSONObjectWithData:responseObject options:kNilOptions error:&error];
errorMessage = [(NSDictionary*)json objectForKey:#"Message"];
}else{
json = [error.userInfo objectForKey:NSLocalizedDescriptionKey];
errorMessage = json;
}
errorBlock(errorMessage, statusObject);
}];
}
What I need is to stop call in ViewdidDisappear View delegate
- (AFHTTPRequestOperation *)GET:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:#"GET" URLString:URLString parameters:parameters success:success failure:failure];
[self.operationQueue addOperation:operation];
return operation;
}
How to solve this particular issue?
I got your point, I think the problem is not about the AFNetWorking or download, it is about how you organize your view controllers.
In short, you need to make sure the synchronization of the data and view.
What cause your crash is when users do some operation(eg. delete, move...), the data is not the same with what view shows.
Let's play back an example:
An array with 12 objects and show it with a table view.
User call a web request to change the array. As we know, it needs time.
User leave and come back again. In this view, table view shows with the old array.
At this point, web request comes back. The array is modified to 10 object.But at this time, the call back dose not cause the table view to load the new data.
When user do some operation, just like delete the 11st object in the table view. Actually, there is no 11st object in array.
So crash comes.
How to deal with it is to keep the synchronization of the data and view.
First get a reference to the Operation object by
AFHTTPRequestOperation *operation = [manager GET:action parameters:nil success:^...blah blah blah...];
Then you can set the completion block to nil when you move away from this screen.
[operation setCompletionBlock:nil];
Please note that even though you move away from the screen, the request may actually execute successfully. However, your app will not crash now.
Thanks RuchiraRandana and childrenOurFuture for your answer, I got help from your answers and finally I come to solution where I am not going to cancel operation and set nil delegate, because my others operation are also in working which is trigger on other screen.
I create a just BOOL and set YES default value in singleton class and also set to no in - (void)dealloc on that particular class and in API class where I am triggering that delegate I added that check.
if ([SHAppSingleton sharedInstance].isDocListControllerPop == YES) {
[delegate documentListResponse:documentList andStatusCode:code];
}
I know this might not be perfect solution but this resolved my issue.
Thanks
I have the following scenario in an application that uses AFNetworking to make services calls:
I call a special service that will generate a token for me
I call the service that I want, sending this token as a parameter
I call another special service to destroy the token.
I have to follow these 3 steps every time I make a request to the server. I cannot change the way the server works, so I have to comply to this requirement. I also cannot use the same token for more than one request.
My question is the following - I tried to accomplish this using AFHTTPRequestOperations:
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.manager.requestSerializer requestWithMethod:#"POST" URLString:[[NSURL URLWithString:#"serviceName.json" relativeToURL:self.manager.baseURL] absoluteString] parameters:#{ #"token": token } error:&serializationError];
AFHTTPRequestOperation *myRequestOperation = [self.manager HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responseObject) {
// Success login
} failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) {
// Failure logic
}];
[myRequestOperation addDependency:createTokenRequestOperation];
where self.manager is an instance of AFHTTPRequestOperationManager, but there is a problem - I do not have a value for token.
Since myRequestOperation should execute only after point 1 from the list above, I make it dependent on the operation that will get me a token.
Now comes my confusion - how can I create an operation that uses a parameter from a previous operation, when I need to have both of them instantiated in order to make the one depend on the other?
Since I was not able to find a solution that will work for me, I ended up using PromiseKit, which allows me to chain asynchronous calls like this:
[NSURLConnection promise:rq1].then(^(id data1){
return [NSURLConnection promise:rq2];
}).then(^(id data2){
return [NSURLConnection promise:rq3];
}).then(^(id data3){
// Work with the data returned from rq3
});
I'm using AFNetworking as network library. There are two different coding styles and I don't know which one is better.
Wrap all functions that associated to network to one file.
For example, I have a singleton file called API.m, and I wrapped login function as below:
- (void) login:(NSString *)username withPassword:(NSString *)password
andCompletionBlock:(void(^)(NSString*))block andFailBlock:(void(^)())failBlock
{
NSMutableString *url = [[NSMutableString alloc] initWithCapacity:10];
[url appendString:LOGINURL];
NSURL* nurl = [NSURL URLWithString:url];
NSURLRequest *request = [NSURLRequest requestWithURL:nurl];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString *requestTmp = [NSString stringWithString:operation.responseString];
block(requestTmp);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failBlock();
}];
[operation start];
}
Then in LoginViewController, I can call this function to do my login job:
[[ServerAPI Instance] login:#"hello" withPassword:#"world"
andCompletionBlock:^(NSString *str) {} failBlock^(){}];
Write the network process code directly in separate view controllers.
Which one is a better coding style?
Creating separate models to handle your API layer is always going to be better than writing networking code in your view controllers. This is especially true when you want to use the same networking call in more than one place.
With that said, there's probably a better way to write your networking instead of a blanket, multi-use singleton. Consider creating individual models such as a User model whose instance represents a logged in User, and also has convenient methods for login, logout, credential saving, refreshing, etc. Therefore you keep code relevant to a user within a the user class.
My assumption is that the operations are running asynchronously on a separate thread, but the loop never exits, so something is not as I assumed.
/**
Checks if we can communicate with the APIs
#result YES if the network is available and all of the registered APIs are responsive
*/
- (BOOL)apisAvailable
{
// Check network reachability
if (!_connectionAvailable) {
return NO;
}
// Check API server response
NSMutableSet *activeOperations = [[NSMutableSet alloc] init];
__block NSInteger successfulRequests = 0;
__block NSInteger failedRequests = 0;
for (AFHTTPClient *httpClient in _httpClients) {
// Send heart beat request
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:#"" parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// Server returned good response
successfulRequests += 1;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Server returned bad response
failedRequests += 1;
}];
[operation start];
[activeOperations addObject:operation];
}
// Wait for heart beat requests to finish
while (_httpClients.count > (successfulRequests + failedRequests)) {
// Wait for each operation to finish, one at a time
//usleep(150);
[NSThread sleepForTimeInterval:0.150];
}
// Check final results
if (failedRequests > 0) {
return NO;
}
return YES;
}
A few suggestions:
Never check reachability to determine if a request will succeed. You should try the request; only if it fails should you consult reachability to try and get a best guess as to why. Reachability makes no guarantee about whether a request will fail or succeed.
Is this method called on the main thread? Even if you fixed the problem with the requests never completing, it will block the UI the entire time your network requests are running. Since these requests can take potentially a long time, this is a bad experience for the user as well as something the OS will kill your app for if it happens at the wrong time (e.g. at launch).
Looping while calling sleep or equivalent is wasteful of CPU resources and memory, as well as prevents the thread's runloop from servicing any timers, event handler or callbacks. (Which is probably why the networking completion blocks never get to run.) If you can avoid blocking a thread, you should. In addition, Cocoa will very often be unhappy if you do this on an NSThread you didn't create yourself.
I see two options:
Use dispatch_groups to wait for all of your requests to finish. Instead of blocking your calling thread, you should instead take a completion block to call when you're done. So, instead of returning a BOOL, take a completion block which takes a BOOL. Something like - (void)determineIfAPIIsAvailable:(void(^)(BOOL))completionBlock;
Get rid of this method altogether. What are you using this method for? It's almost certainly a better idea to just try to use your API and report appropriate errors to the user when things fail rather than to try to guess if a request to the API will succeed beforehand.
I believe the issue is that I was not using locking to increment the counters so the while loop would never evaluate to true.
I was able to get it working by only looking for a fail count greater than 0 that way as long as it was incremented by any of the request callback blocks then I know what to do.
I just so happen to have switched to [NSOperationQueue waitUntilAllOperationsAreFinished].
Final code:
/**
Checks if we can communicate with the APIs
#result YES if the network is available and all of the registered APIs are responsive
*/
- (BOOL)apisAvailable
{
// Check network reachability
if (!_connectionAvailable) {
return NO;
}
// Check API server response
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
__block NSInteger failedRequests = 0;
for (AFHTTPClient *httpClient in _httpClients) {
// Send heart beat request
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:#"" parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// Server returned good response
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Server returned bad response
failedRequests += 1;
}];
[operationQueue addOperation:operation];
}
// Wait for heart beat requests to finish
[operationQueue waitUntilAllOperationsAreFinished];
// Check final results
if (failedRequests > 0) {
return NO;
}
return YES;
}
I would like to find out if it's possible to avoid duplicate HTTP requests with AFNetworking. Specifically, my app may generate multiple HTTP requests which all have the same url. I would like to prevent AFNetworking from processing duplicates of the same url.
Im not sure if this can be done in AFNetworking or the underlying iOS sdk. I understand that i could manually keep trac of pending url request and avoid duplicates that way, but was wondering if there is a lower level functionality already available to take care of this.
Thanks.
Your best bet is to subclass AFHTTPRequestOperationManager's HTTP request operations and keep track of them there if you want to track requests the same way for each request, otherwise the logic will need to be elsewhere.
AFNetworking doesn't support this because there is probably some logic relevant to when you should and when you should not execute a duplicate request, which would be highly customizable (not generic enough for the framework)
I made a category that checks for in-progress GET requests before making new ones.
https://github.com/NSElvis/AFHTTPSessionManager-AFUniqueGET
It does this by using the method getTasksWithCompletionHandler of the session.
I had the same problem. I have a chat-application and I need to show user avatar for each message. So I made few same requests and I've resolved this issue.
First, I add NSDictionary with NSString avatar URLs keys and completion blocks objects:
#property (strong, nonatomic) NSMutableDictionary* successBlocksDictForGetAvatar;
And here's my method to get user avatar image:
- (void)getAvatarForUser:(ETBUser*)user
completion:(void(^)())completionBlock
{
if (user.avatarURL)
{
NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:user.avatarURL]];
if (self.successBlocksDictForGetAvatar[user.avatarURL])
[self.successBlocksDictForGetAvatar[user.avatarURL] addObject:completionBlock];
else
{
NSMutableSet* set = [[NSMutableSet alloc] initWithObjects:completionBlock, nil];
[self.successBlocksDictForGetAvatar setObject:set forKey:user.avatarURL];
AFHTTPRequestOperation* operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
UIImage* avatarImage = [UIImage imageWithData:responseObject];
if (avatarImage)
{
user.avatar = avatarImage;
[[DataManager shared] saveAvatar];
[((NSSet*)self.successBlocksDictForGetAvatar[user.avatarURL]) enumerateObjectsUsingBlock:^(void(^successBlock)(), BOOL *stop) {
successBlock();
}];
[self.successBlocksDictForGetAvatar removeObjectForKey:user.avatarURL];
}
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self.successBlocksDictForGetAvatar removeObjectForKey:user.avatarURL];
}];
[self.manager.operationQueue addOperation:operation];
}
}
}
Here I check if my dictionary contains request. If YES, I add completion block for user in dictionary. Otherwise I setObject:forKey: and make AFNetworking request. In success and fail blocks I clean my dictionary.
P.S. Here's my manager getter:
- (AFHTTPRequestOperationManager*)manager
{
if (!_manager)
{
_manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:kBaseURL];
[_manager.requestSerializer setValue:NetworkConstantsHeaderAcceptValue forHTTPHeaderField:NetworkConstantsHeaderAcceptKey];
[_manager.operationQueue setMaxConcurrentOperationCount:1];
}
return _manager;
}