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.
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 have a method to download a JSON result from a server regularly so I would like to learn how to input parameters as opposed to keep typing out the same method over and over!
Here is what I have so far:
-(void)downloadData:(NSString *)saveto downloadURL:(NSString *)URL parameters:(NSString *)params{}
It is mostly working ok, except where I am attempting to save my result. I wish to store my result in an array called "locations", I am trying to pass the name locations in the "saveto" NSString, but am not sure how to do so?
Originally I used:
locations =[[NSMutableArray alloc] init];
I would like to somehow pass the name of the array I wish to save to so like this?:
saveto = [[NSMutableArray alloc] init];
Example to run method:
[self downloadData:[NSString stringWithFormat:#"locations"] downloadURL:[NSString stringWithFormat:#"http://test.com:80/test/locations.php"] parameters:[NSString stringWithFormat:#"welcome=hi"]];
You haven't shown us how you're retrieving the data (synchronously or asynchronously?) nor how you're building that request (it's curious that params and saveto and URL are all string parameters).
But I'd suggest doing it asynchronously (since you never want to block the main queue). And in that case, you provide a "block" parameter that the caller can specify the block of code to run when the download is done.
So, you might have downloadData that looks something like:
- (void)downloadDataWithURL:(NSURL *)URL parameters:(NSDictionary *)parameters completion:(void (^)(NSArray *array, NSError *error))completion
{
// build your request using the URL and parameters however you want
NSURLRequest *request = ...;
// now issue the request asynchronously
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (!data) {
completion(nil, connectionError);
return;
}
NSError *parseError = nil;
NSArray *array = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
completion(array, parseError);
}];
}
And can be invoked as:
NSURL *URL = [NSURL URLWithString:#"http://test.com:80/test/locations.php"];
NSDictionary *parameters = #{#"welcome" : #"hi"};
[self downloadDataWithURL:URL parameters:parameters completion:^(NSArray *array, NSError *error) {
if (error) {
NSLog(#"downloadDataWithURL error: %#", error);
return;
}
self.locations = array;
}];
FYI, I made the URL to be a NSURL object (to conform to common practice). I also made the parameters a NSDictionary (so that if you're making a GET request or POST request of type application/x-www-form-urlencoded, you can more easily do the necessary CFURLCreateStringByAddingPercentEscapes; if it's JSON request, this also makes it easier to make a proper JSON request).
But feel free to change the parameters URL and parameters to be whatever type makes sense for your particular implementation, but hopefully this illustrates the idea. Add an additional completion block parameter which will be the block of code that will be run when the download is done, and then the caller do whatever it wants with the results.
You should not reinvent the wheel and you should use one of the frameworks like AFNetworking or RestKit to do this stuff for you. In addition to doing the work for you it gives you very powerful tools like reachability and error handling and most importantly it handles converting to and from JSON (and many other formats). When you use AFNetworking you get an NSDictionary called responseObject that you can use in many ways including setting an NSString. Most of the time you will simply use that dictionary as the dataSource for your UI so all of the work is done for you.
AFNetworking code looks like this:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://example.com/resources.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
As you can see in only a couple of lines of code you handle success, failure, and the creation of an object with all your info. No need for [[NSDictionary alloc] init]
This is one of the many many great tutorials on AFNetworking
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.
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];
So I am trying to upload a file (picture) to the a server using their web service called UploadFile which takes 2 variables.
FileInfo info and a int requestId (which in my case will always be 0)
The FileInfo object contains several variables name (String), description(String), content(binary data of the file, in this case it would be the image), id (String), and name (String)
How do I interface with this server to make the request go through? Normally when I have been pulling/posting information I have just been doing JSON calls, but I am guessing uploading is different. I am not sure how to do this when the service requires a custom object to be passed.
Do I need to create the object in my App?
I am trying to use AFNetworking's AFHTTPRequestOperations for this.
I am trying to use their example as a springboard, but I still need to make sure on what to change in their upload example since the example uses direct uploading of an image and I need to upload a FileInfo object instead of a jpeg.
NSURL *url = [NSURL URLWithString:#"https://SomeDomain.com/Services/FileService.svc/UploadFile"];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
NSData *imageData = UIImageJPEGRepresentation([self.photoImageView image], 0.5);
NSMutableURLRequest *request = [httpClient multipartFormRequestWithMethod:#"POST" path:#"/upload" parameters:nil constructingBodyWithBlock: ^(id <AFMultipartFormData>formData) {
[formData appendPartWithFileData:imageData name:#"avatar" fileName:#"Upload.jpg" mimeType:#"image/jpg"];
}];
NSLog(#"Request %#", [request description]);
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
NSLog(#"Operation: %#", [operation description]);
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
NSLog(#"Sent %lld of %lld bytes", totalBytesWritten, totalBytesExpectedToWrite);
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"RESPONSE: %#", [responseObject description]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failed: %#", [error description]);
}];
[httpClient enqueueHTTPRequestOperation:operation];
Thanks,
Alan
if you see the AFNetworking documentation of the methode multipartFormRequestWithMethod...you have a Dictionnary parameter where you can putt all the infos needed by the Web service.
/** Creates an NSMutableURLRequest object with the specified HTTP
method and path, and constructs a multipart/form-data HTTP body,
using the specified parameters and multipart form data block. See
http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.2
Multipart form requests are automatically streamed, reading files
directly from disk along with in-memory data in a single HTTP body.
The resulting NSMutableURLRequest object has an HTTPBodyStream
property, so refrain from setting HTTPBodyStream or HTTPBody on
this request object, as it will clear out the multipart form body
stream. #param method The HTTP method for the request. This
parameter must not be GET or HEAD, or nil. #param path The path
to be appended to the HTTP client's base URL and used as the request
URL. #param parameters The parameters to be encoded and set in the
request HTTP body. #param block A block that takes a single argument
and appends data to the HTTP body. The block argument is an object
adopting the AFMultipartFormData protocol. This can be used to
upload files, encode HTTP body as JSON or XML, or specify multiple
values for the same parameter, as one might for array values.
#return An NSMutableURLRequest object */
Refer this. They have given different methods:
https://github.com/AFNetworking/AFNetworking