So I am trying to handle posting something to server and this works while the iPhone is with internet. Now I need to handle the request if there is no internet. I tried a bit of code below tho handle operation queue in relation to reachability. I dont think I need cache as I'm posting. What am I missing here? I want the request to be sent soon as the internet is available. While I might be doing something else in the app. The only thing I suspect is that my OperationManager is initialised in each request like POST or GET so maybe the previous one could be wipeout. But that is operation manager and not operation queue.
- (void)sendRequestPOST:(NSString *)url parameters:(NSMutableDictionary *)parameters {
NSLog(#"POST Request URL : %#", url);
NSLog(#"POST Request Body : %#", parameters);
__block AFHTTPRequestOperation *opr;
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
AFJSONRequestSerializer *jsonRS = [[AFJSONRequestSerializer alloc] init];
[manager setRequestSerializer:jsonRS];
[[manager requestSerializer] setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[[manager requestSerializer] setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[[manager responseSerializer] setAcceptableContentTypes:[NSSet setWithObjects:#"application/json", #"text/html", nil]];
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(#"Reachability: %#", AFStringFromNetworkReachabilityStatus(status));
}];
NSOperationQueue *operationQueue = manager.operationQueue;
[manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN:
case AFNetworkReachabilityStatusReachableViaWiFi:
[operationQueue setSuspended:NO];
break;
case AFNetworkReachabilityStatusNotReachable:
default:
[operationQueue setSuspended:YES];
break;
}
}];
[manager.reachabilityManager startMonitoring];
[manager POST:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
[MBProgressHUD hideHUDForView:self.mainView animated:YES];
opr = operation;
[self.mainView setUserInteractionEnabled:YES];
NSLog(#"POST Response : %#",responseObject);
NSLog(#"Status Code : %ld",(long)[[operation response] statusCode]);
if([_delegate respondsToSelector:#selector(didFinish:dictInfo:)]) {
[AFNetworkActivityIndicatorManager sharedManager].enabled = NO;
[_delegate didFinish:self dictInfo:(NSMutableDictionary *)responseObject];
NSLog(#"Request finished");
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
opr = operation;
[MBProgressHUD hideHUDForView:self.mainView animated:YES];
[self.mainView setUserInteractionEnabled:YES];
NSLog(#"POST Error: %#", error);
// [[AFHTTPRequestOperationManager manager].operatonQueue addOperation:operation];
if([_delegate respondsToSelector:#selector(failRequest:statusCode:)]) {
[AFNetworkActivityIndicatorManager sharedManager].enabled = NO;
[_delegate failRequest:self statusCode:[[operation response] statusCode]];
NSLog(#"Request failed");
}
}];}
I wrote a Swift pod that allows network requests to be wrapped in a way that they can be enqueued regardless of current network connectivity and saved until it goes through - https://cocoapods.org/pods/OfflineRequestManager. Might be useful to anybody seeing this.
The simplest use case would look something like the following, though most actual cases (saving to disk, specific request data, etc.) will have a few more hoops to jump through:
import OfflineRequestManager
class SimpleRequest: OfflineRequest {
func perform(completion: #escaping (Error?) -> Void) {
doMyNetworkRequest(withCompletion: { response, error in
handleResponse(response)
completion(error)
})
}
}
///////
OfflineRequestManager.defaultManager(queueRequest: SimpleRequest())
Related
I have this bool method that returns a yes or no for an inputted string.
I'm successfully able to return a YES or a NO, but I cannot seem to able to make a network connection and return a YES or a NO depending on the server's response.
I tried using __block and I don't feel like that will wait for the web request to finish, is there a way to return YES or NO in the success block without it giving me the error:
Incompatible block pointer types sending 'BOOL(^)(NSURLSessionTask*__strong, NSError __strong' to parameter of the type 'void(^)(NSURLSessionTask...)
-(BOOL)customResponseForString:(NSString *)text {
__block BOOL response_available;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager.responseSerializer setAcceptableContentTypes:[NSSet setWithObject:#"text/plain"]];
[manager GET:[NSString stringWithFormat:#"http://example.com/response.php?input=%#", text] parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseObject options:NSUTF8StringEncoding error:nil];
response_available = (BOOL)response[#"response_available"];
if (response_available) {
[session sendTextSnippet:response[#"response"] temporary:NO scrollToTop:NO dialogPhase:#"Summary"];
} else {
response_available = NO;
}
[session sendTextSnippet:[[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding] temporary:NO scrollToTop:NO dialogPhase:#"Summary"];
[session sendRequestCompleted];
} failure:^(NSURLSessionDataTask *task, NSError *error) {
//return NO;
}];
});
return response_available;
}
Your block definition syntax is probably erroneous, because you can definitely return a BOOL along other parameters in a block.
- (void)fetchCurrentUserWithCompletion:(void (^)(BOOL success, User *user))completion;
This method would be called like this:
[self.userProfileController fetchCurrentUserWithCompletion:^(BOOL success, User *user) {
if (success) {
NSLog(#"Current User Name: %#", user.fullName);
}
}];
If you use AFNetworking, check the AFHTTPRequestOperation object that handle completionBlocks:
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
User *user = [self userFromResponseObject:responseObject];
if (completion) completion(YES, user);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (completion) completion(NO, user);
}];
Because you are implicitly initializing response_available to NO and then using an async GCD call, your method as written will always immediately return NO without waiting for the request to finish. Note: switching to dispatch_sync won't help either because AFNetworking will queue the GET request asynchronously either way.
Best Approach
Add a completion block argument to customResponseForString:. Then simply execute your completion block in the success or failure blocks of the AFHTTPRequestOperation.
Workable Approach (use caution!)
It is possible to make customResponseForString: wait for a response to the network request, but you will have significant issues if it is ever called from the main thread.
First you create a dispatch group and tell it you are starting some long-running work:
dispatch_group_t networkGroup = dispatch_group_create();
dispatch_group_enter(networkGroup);
Then you need to make your network request and when it completes tell the group that the work is finished with dispatch_group_leave():
[manager GET:[NSString stringWithFormat:#"http://example.com/response.php?input=%#", text] parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseObject options:NSUTF8StringEncoding error:nil];
response_available = (BOOL)response[#"response_available"];
if (response_available) {
[session sendTextSnippet:response[#"response"] temporary:NO scrollToTop:NO dialogPhase:#"Summary"];
} else {
response_available = NO;
}
[session sendTextSnippet:[[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding] temporary:NO scrollToTop:NO dialogPhase:#"Summary"];
[session sendRequestCompleted];
dispatch_group_leave(networkGroup);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
response_available = NO;
dispatch_group_leave(networkGroup);
}];
Before your original method returns, tell it to wait for the entire group to finish processing:
dispatch_group_wait(networkGroup, DISPATCH_TIME_FOREVER);
return response_available;
You could adjust this time interval as needed or leave it at DISPATCH_TIME_FOREVER to let the network request time out on its own.
I've been trying to come up with a solution to queue HTTP requests using AFNetworking when the device is offline, so when it goes back online the requests gets done. As far as I've been able to understand, this is possible setting the setReachabilityStatusChangeBlock: parameter.
So far this is what I have:
// ViewController.h
#interface XYZTicketViewController : UIViewController<NSURLConnectionDelegate> // This is from before I started using AFNetworking, I'm intending to change all the requests to use AFNetworking in the near future.
#end
// ViewController.m
(...)
#import <AFHTTPRequestOperationManager.h>
#import <AFNetworkReachabilityManager.h>
(...)
#interface XYZTicketViewController ()
- (void)viewDidLoad
(...)
{
NSURL *baseURL = [NSURL URLWithString:#"http://54.213.167.202"];
AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL];
NSOperationQueue *operationQueue = manager.operationQueue;
[manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN:
case AFNetworkReachabilityStatusReachableViaWiFi:
[operationQueue setSuspended:NO];
NSLog(#"WIFI");
break;
case AFNetworkReachabilityStatusNotReachable:
default:
[operationQueue setSuspended:YES];
NSLog(#"oflline, baby");
break;
}
}];
NSDictionary *parameters = #{#"action": #"login", #"user": #"mail.address#gmail.com", #"pass": #"howdoyouturnthison"};
[manager GET:#"http://54.213.167.202/api.php" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
(...)
}
I cannot find any example but I read here that it is possible, but so far anything happens when the online status changes.
Hope you can help me out
You need to call startMonitoring, before you call setReachabilityStatusChangeBlock
[manager.reachabilityManager startMonitoring];
If you are using AFNetworking 2.0, I suggest following:
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
DLog(#"Reachability: %#", AFStringFromNetworkReachabilityStatus(status));
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN:
case AFNetworkReachabilityStatusReachableViaWiFi:
[operationQueue setSuspended:NO];
NSLog(#"WIFI");
break;
case AFNetworkReachabilityStatusNotReachable:
default:
[operationQueue setSuspended:YES];
NSLog(#"offline, baby");
break;
}
}];
You do not store manager. So it is like any local variable is deleted when leaving viewDidLoad. Store it to property or instance variable.
I am trying out afnetworking 2.0 and just trying to figure out how to cancel specific tasks.
The old way would be to use something like
[self cancelAllHTTPOperationsWithMethod:#"POST" path:#"user/receipts"]
but I dont see anything like this in 2.0
I created a sub class of AFHTTPSessionManager which gives me access to the array of pending tasks and I can cancel them directly but I dont know how to identify 1 task from another so I can cancel only specific tasks.
Task does have an taskidentifier but this doesnt appear to be what I need.
NSString *path = [NSString stringWithFormat:#"user/receipts"];
[self.requestSerializer setAuthorizationHeaderFieldWithUsername:[prefs valueForKey:#"uuid"] password:self.store.authToken];
[self GET:path parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
completionBlock(responseObject);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
errorBlock(error);
}];
now if i wanted to cancel this request only how would I approach this?
You can store the task in a variable so you can access it later:
NSURLSessionDataTask* task = [self GET:path parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
completionBlock(responseObject);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
errorBlock(error);
}];
Then simply cancel it with [task cancel].
Another way would be to save the task ID of the task and later ask the URL session for its tasks and identify the task you wish to cancel:
// save task ID
_savedTaskID = task.taskIdentifier;
// cancel specific task
for (NSURLSessionDataTask* task in [self dataTasks]) {
if (task.taskIdentifier == _savedTaskID) {
[task cancel];
}
}
No need to save it, here is my implementation, use your subclass of AFURLSessionManager for cancelling specific request:
- (void)cancelAllHTTPOperationsWithPath:(NSString *)path
{
AFURLSessionManager * yourSessionManager = [self getSessionManager];
[[yourSessionManager session] getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
[self cancelTasksInArray:dataTasks withPath:path];
[self cancelTasksInArray:uploadTasks withPath:path];
[self cancelTasksInArray:downloadTasks withPath:path];
}];
}
- (void)cancelTasksInArray:(NSArray *)tasksArray withPath:(NSString *)path
{
for (NSURLSessionTask *task in tasksArray) {
NSRange range = [[[[task currentRequest]URL] absoluteString] rangeOfString:path];
if (range.location != NSNotFound) {
[task cancel];
}
}
}
you can do the following
NSArray *operations = [[[MyClient sharedClient] operationQueue] operations];
if(operations && operations.count > 0){
for (NSOperation *operation in operations) {
if([operation isKindOfClass:[AFHTTPRequestOperation class]]){
AFHTTPRequestOperation *httpOperation = (AFHTTPRequestOperation *)operation;
NSLog(#"%#", [[httpOperation request] URL]);
//--- if this is your request then cancel it --> [httpOperation cancel];
}
}
}
Where MyClient is a child of AFHTTPClient and the function sharedClient is a static function which returns a singleton instance of MyClient
This is the method I use to do HTTP GET request. How can I change it to use HTTPS?
AS_NET_BASE_URL = http://www.myapp.com
Is it just as simple as replacing the http:// with https:// ?
Thank you in advance
+(void)startGETRequestAtUrlRoute:(NSString *)route withParameters:(NSString *)slashSeparatedParams completion:(void (^)(BOOL, id))completion{
//If internet is rachable, start request
if ([self isInternetReachable]) {
//Where request is going
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:AS_NET_BASE_URL]];
NSString *path = [NSString stringWithFormat:#"%#%#%#", AS_NET_BASE_URL, route, slashSeparatedParams];
//Tell operation to expect JSON
[httpClient registerHTTPOperationClass:[AFJSONRequestOperation class]];
[httpClient setDefaultHeader:#"Accept" value:#"application/json"];
//Start spinner
[self startNetworkIndicator];
//Set up actual GET request
[httpClient getPath:path
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
//Stop spinning
[self stopNetworkIndicator];
//Make the response JSON valid
if (responseObject) {
completion(YES, responseObject);
}
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self stopNetworkIndicator];
completion(NO, NULL);
//Error
NSLog(#"%#", error);
}
];
//No internet connection
}else{
completion(NO, NULL);
}
}
Yes.
HTTP/HTTPS is determined by the scheme of the URL, which in your case is specified in AS_NET_BASE_URL.
At this moment I have a method that calls for the download of data from the web using AFHTTPRequestOperation like so:
- (void)downloadDataForRegisteredObjects:(BOOL)useUpdatedAtDate {
NSLog(#"downloadDataForRegisteredObjects");
NSMutableArray *operations = [NSMutableArray array];
for (NSString *className in self.registeredClassesToSync) {
NSDate *mostRecentUpdatedDate = nil;
if (useUpdatedAtDate) {
mostRecentUpdatedDate = [self mostRecentUpdatedAtDateForEntityWithName:className];
}
NSMutableURLRequest *request = [[SDAFParseAPIClient sharedClient] GETRequestForAllRecordsOfClass:className updatedAfterDate:mostRecentUpdatedDate];
AFHTTPRequestOperation *operation = [[SDAFParseAPIClient sharedClient] HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) {
if ([responseObject isKindOfClass:[NSDictionary class]]) {
// Write JSON files to disk
[self writeJSONResponse:responseObject toDiskForClassWithName:className];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Request for class %# failed with error: %#", className, error);
[[NSNotificationCenter defaultCenter]
postNotificationName:kSDSyncEngineSyncINCompleteNotificationName
object:nil];
}];
[operations addObject:operation];
}
[[SDAFParseAPIClient sharedClient] enqueueBatchOfHTTPRequestOperations:operations progressBlock:^(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations) {
} completionBlock:^(NSArray *operations) {
// Process JSON into CD
if (useUpdatedAtDate) {
[self processJSONDataRecordsIntoCoreData];
}
}];
}
From what I understand, we create an NSURLMutableRequest, pass it to an AFHTTPRequestOperation with a success & failure block.
The success block says, if and when successful, test if dictionary and if so, write it to disk. The failure block says, log the error and post a notification.
The method gets called twice in my app, in series, one after the other. The first time it returns an empty responseObject but the second time it returns a full responseObject.
Why should that be the case?