Got a comment to make the main question a separate ticket and separate each question.
I am trying to write code for an SDK. I need to make API calls.
I am using AFNetworking 2.0 to send a POST request to the server:
NSDictionary * params = [self formDictionaryFromCard: card];
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] requestWithMethod:#"POST" URLString: [self apiUrl] parameters: params];
[request addValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request addValue:#"Firefox" forHTTPHeaderField:#"User-Agent"];
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
op.responseSerializer = [AFJSONResponseSerializer serializer];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
successBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
errorBlock(error);
}];
[[NSOperationQueue mainQueue] addOperation:op];
Is this the correct way to use AFNetworking to make API calls for an SDK?
How do I provide support for https?
Is this the correct way to use AFNetworking to make API calls for an
SDK?
Your code will work as-is. There are a few suggestions I'd make to change it.
Main Queue
Although the sample code on CocoaDocs shows mainQueue being used, consider if you want to use a different NSOperationQueue besides mainQueue. Here are a few possible reasons:
If you'll need to go through and find one of your operations later (for example, if you want to cancel/pause uploads.)
If you use mainQueue for anything else, and you don't want the priority of those operations to be compared to the priority of your network operations when the system looks at which operation to start next
If you'd like more than 1 network request to run at a time (for example, if you want to be able to download a photo and post a message at the same time).
You could use AFNetworking's built-in operation queue (on AFHTTPRequestOperationManager):
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager.operationQueue addOperation:op];
You could also use one of your own.
Check for blocks before calling them
You may want to check for the presence of blocks before calling them to void crashes:
if (successBlock) {
successBlock(responseObject);
}
Avoid redundant code
If all or most of your operations will require these customizations to the header, it's probably easier to subclass AFHTTPRequestOperationManager, and override HTTPRequestOperationWithRequest: success: failure: to add your headers there. Then you can use AFHTTPRequestOperationManager's convenience methods (the ones that begin with POST and GET).
Take a look at the documentation for AFHTTPRequestOperationManager.
How do I provide support for https?
For most uses, simply include https in your URL (in your case, in [self apiUrl]). For specific uses, such as to allow invalid certs, or to only accept specific ones, you will need to look into the AFSecurityPolicy class.
Related
I am starting learning about web service, where I use one url api to get data and to display in my table view. But I saw some tutorials - in that they use NSURLConnection or Rest API or AFNetworking.
I am really confused about all type. Which one should I use in that above type. For web service which type should I use. And also I saw some doubts in SO that use synchronous or asynchronous. Thus this any another type to get data from URL?
Actually for all web service, which should I use to get data and display?
-(void)JsonDataParsing
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager POST:url parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSDictionary *jsonDict = (NSDictionary *) responseObject;
//!!! here is answer (parsed from mapped JSON: {"result":"STRING"}) ->
NSString *res = [NSString stringWithFormat:#"%#", [jsonDict objectForKey:#"result"]];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//....
}
];
}
Firstly, AFNetworking and NSURLConnection are used on Mobile Side.
Rest API is not from mobile side. Rest API you implemented on server side which handle CRUD operations like GET, POST, PUT and DELETE.
Third party libraries are there to ease our work. And AFNetworking is very popular and trustworthy library.
AFNetworking makes asynchronous network requests. To read more about it, visit Introduction to AFNetworking.
AFNetworking does everything NSURLConnection can. Using it now will save you a lot of time writing boilerplate code!
NSURLConnection and NSURLSession is apple API use to manage network operation like download and upload, AFNetworking is the framework that use those 2 API and added multithreading/error handling/network reachability....to make your life easier, RESTful is the architecture for client-server connecting, u can implement it in your serverside to return things back to your clientside in easy to use model (JSON).
synchronous mean u wait for it to complete to do anything else, asynchronous means u just start it but don't need to wait for it, like u do a request to server and user still can interact with your UI at the same time, so its advised that use asynchronous task to request to server then only update the UI in synchronous
hope my explain is easy to understand and correct :)
NSURLConnection
This lets you to load the content of URL by providing the URL request object. By using NSURLConnection you can load URL requests both asynchronously using a callback block and synchronously. See this example
NSURL *URL = [NSURL URLWithString:#"http://example.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// ...
}];
For more you can go to apple docs
AFNetworking
This is third party library built on the top of Foundation URL Loading.
This is very easy to install through pods and handy to use. See below example like how I am using the same in my app
-(AFHTTPRequestOperationManager *)manager
{
if (!_manager)
{
_manager = [AFHTTPRequestOperationManager manager];
_manager.requestSerializer = [AFHTTPRequestSerializer serializer];
_manager.responseSerializer = [AFHTTPResponseSerializer serializer];
}
return _manager;
}
Above we are initializing the instance of AFHTTPRequestOperationManager *manager
[self.manager POST:#"http://example.com" parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSError *error;
NSMutableDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:&error];
// return response dictionary in success block
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
// return error in failure block
}]
Above method will load data asynchronously and remaining is self explanatory. But if you want to block the user interface like a synchronous request than use [operation waitUntilFinished] which is an anti-pattern. Here operation is a instance of AFJSONRequestOperation.
I am trying to get some design/code advise here.
I have an app which needs to call 3 apis of a web service. These happen in different views but I need all the information from these 3 apis to be in one object. So I pass an object from view controller to another and add data to it as I make the https calls to the web service.
I created a function called PostToServer, that calls the webapi based on the "type" and picks the appropriate URL to use for the post. But there will only be one didReceiveData call. I am storing the "type" of api in a variable in the object so that inside the didReceiveData I can parse the response appropriately.
Is there a better way to use the same NSURLConnection code to process multiple webapi calls? I am new to obj-c and so want to make sure I am using the language constructs properly.
Their is one delegate of NSURLConnection class " - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response " and in it their is a parameter response and this response has a property "URL" by which you can get to know that this response is for which URL request.
The "didReceiveResponse" method gets called once and then "didReceiveData" gets called, so you can make your check and logic accordingly.
I use AFNetworking instead of NSURLConnection. If you need 3 requests to build one object you can pass this object from one connection to another and add missing data.
NSString *urlString = ...
NSDictionary *parameters = #{#"username": username,
#"password": password,
};
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] init];
[manager.requestSerializer setTimeoutInterval:10.0];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [manager.responseSerializer.acceptableContentTypes setByAddingObject:#"text/json"];
[manager POST:urlString parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
Person *person = [Person new];
person.name = [responseObject valueForKey:#"name"];
person.city = [responseObject valueForKey:#"city"];
[self callSecondRequest:person];
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}];
}
Method callSecondRequest: will add data from another request to the object.
Another option is to run simultaneously 3 requests which would share the same object.
I have a problem when users use my app and they lost connection or use airplane mode.
My app server side doesn't set any cache policy and for the time being I can't change it. I migrated from AFNetworking 1.x to 2.0 and now I'm using AFHTTPRequestOperationManager when making requests. The problem is that because I have no cache policy on the server side, every request is made to the server (which for now is fine) but if the user is not able to connect to my server, it doesn't load the cached request.
So, I'm trying the following, using AFHTTPRequestOperation directly like this:
NSURL *URL = [NSURL URLWithString:filePath];
NSMutableURLRequest *request = [[NSURLRequest requestWithURL:URL] mutableCopy];
if (![[AFNetworkReachabilityManager sharedManager] isReachable]) {
[request setCachePolicy:NSURLRequestReturnCacheDataElseLoad];
}
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
[[NSOperationQueue mainQueue] addOperation:op];
This way, if AFNetworkReachabilityManager tells me there's no connectivity, I configured the cache policy for the request and it is loaded from the cache correctly.
The question is, is this the right approach to this situation?
You just need to subclass the AFHTTPSessionManager and check if the client is offline. Then you can change the cache policy or force the app to use cached data.
I want to upload video using PUT request and this is my experience.
I was trying many possibilities of doing PUT request using AFNetworking 2.0, but everything fails.
But I resolved this and this "Question" is information for everybody else who is asking solution.
This works for me:
NSDictionary *headersDict = #{#"Content-Length": [NSString stringWithFormat:#"%#", mySize], #"Content-Type": #"video/mp4", #"Accept": #"application/json", #"Authorization": [NSString stringWithFormat:#"Basic %#", hash]};
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:urlToPut];
[request setHTTPMethod:#"PUT"];
[request setHTTPBody:videoData];
[request setAllHTTPHeaderFields:headersDict];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// if everything run great, we have to invalidate timer to notify
[uploadTimer invalidate];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", error);
}];
// start timer where app will be checking upload progres on the server
uploadTimer = [NSTimer scheduledTimerWithTimeInterval:.1
target:self
selector:#selector(checkUploadProgressForRecord:)
userInfo:record
repeats:YES];
[operation start];
Anybody has another solution?
You approach has the following issues:
The PUT method is not the suggested method for uploading a file. PUT can be used, but this requires that the server MUST create the resource at the specified URI. In most cases however, the server will create a new URI (which is unknown to the client) when saving the uploaded file locally on the server. In this case, a POST method must be used.
You are using a NSData object which represents the content of the file and assign it the request with setHTTPBody. This is problematic regarding system memory when the file is large and the NSData object uses a heap allocated buffer to hold the file's content. You should either use a NSData object which uses a memory mapped file or a NSStream to represent the file content.
You are too vague about the request headers:
`[request setAllHTTPHeaderFields:headersDict];`
Request header are important. You should set the Content-Type and possible the Content-Length header.
For "Form-based File Upload", see also RFC 1867.
I have seen a lot of major changes with the RestKit framework in version 0.20.x for the iOS platform.
The one thing I haven't found so far on the web is an example of how to download a binary file with the new version of RestKit.
I need to send a JSON object to a REST service and expect a binary file in return. Would seem simple, wouldn't it but for some reason RestKit only expects JSON (and the common internet content types such as XML) to come back.
The JSON object essentially is a request object telling the service which image it should go and get for me.
Fortunately I have managed to use the underlying AFNNetworking framework to help me with this and leverage the RestKit serializer to produce the request object I needed.
MyRequestClass *request = // ... get my request class instance
RKObjectManager *manager = [RKObjectManager sharedManager];
NSMutableURLRequest *downloadRequest = [manager requestWithObject:request method:RKRequestMethodPOST path:ROUTE_URL_MY_SERVICE parameters:nil];
AFHTTPRequestOperation *requestOperation = [[AFImageRequestOperation alloc] initWithRequest:downloadRequest];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// Use my success callback with the binary data and MIME type string
callback(operation.responseData, operation.response.MIMEType, nil);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Error callback
callback(nil, nil, error);
}];
[manager.HTTPClient enqueueHTTPRequestOperation:requestOperation];