Downloading a binary file with RestKit 0.20.x - ios

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];

Related

About web service Json/rest api/Nsurl connection

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.

AFNetworking 2 Response Error (Content type: text/html and not JSON)

After trying nearly every response on the subject, I've come up without a working answer to my problem.
The problem: So I've implemented the uploading portion of my app using AFNetworking 2.0.3 after porting from AFNetworking 1.3:
-(void)commandWithParams:(NSMutableDictionary*)params onCompletion:(JSONResponseBlock)completionBlock {
NSData* uploadFile = nil;
if ([params objectForKey:#"file"]) {
uploadFile = (NSData*)[params objectForKey:#"file"];
[params removeObjectForKey:#"file"];
}
AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:[NSURL URLWithString:#"http://54.204.17.38"]];
manager.responseSerializer = [AFJSONResponseSerializer serilizer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"application/json"];
AFHTTPRequestOperation *apiRequest = [manager POST:#"/API" parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
if (uploadFile) {
[formData appendPartWithFileData:uploadFile name:#"file" fileName:#"photo.jpg" mimeType:#"image/jpeg"];
}
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
completionBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
completionBlock([NSDictionary dictionaryWithObject:[error localizedDescription] forKey:#"error"]);
}];
[apiRequest start];
}
The error I get when using this code is "Request failed: unacceptable content-type: text/html" I know you might be wondering if the server is responding with proper JSON, and I have every reason to think it is after inspecting the response headers in my browser that say 'MIME type: application/json'. Also, I am using 'header('Content-type: application/json')' at the top of my API as well (PHP API). Now, if I change the serialization type to 'AFHTTPResponseSerializer' instead of 'AFJSONResponseSerializer', it will not spit out the JSON error, but it will give me a different error (a random unrecognized selector error).
Any thoughts on why I cannot seem to get a JSON response out of this method?
You can set the AFHTTPSessionManager to accept any MIME Type:
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"text/html"];
Got it! So, turns out, unknowingly, although my API was returning valid JSON, matter examining the header response logged on the Xcode side of things (thru NSLog(#"Error: %#", error);), it was actually returning text/HTML because it wasn't actually hitting the correct file, it was getting re-routed by a header somewhere. After explicitly stating the API path to be /API/index.php and not just /API, it started returning the valid JSON! Next, after making sure the response was properly JSON serialized (using requestManager.responseSerializer = [AFJSONResponseSerializer serializer];), the app worked!
Hopefully this helps someone who was having the same issue :)

PUT request with data using AFNetworking 2.0

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.

Using AFNetworking to make API calls

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.

Sending Images and data to PHP server-side

It,s my first time trying to send Images & Data to a PHP Server from an app, and I am quite confused as to what's the best way to achieve this.
In my research, I came across AFNetworking library and this base64 library (which I would take suggestion in another one), but I don't know if I can achieve what I want with that or how to implement it.
What I want to do is to send data & images that have a relationship.
Lets say the User has to upload their user details + their picture and their house details + a picture
my JSON would be something like
{
"userDetails": { "name":"jon",
"surname":"smith",
"phone":"123412",
"userPic":"base64pic"
},
"house": { "address":"123 asd",
"postcode":"w2 e23",
"housePic":"base64pic"
}
}
of course that JSON would also have to include security validations.
My problem comes when I would like to avoid using base64 encoding given the 33% size increase and I don't know how I could send the same information to PHP.
I get very confused when trying to send images & data that have a relationship and should be stored taking that relationship into account in the server.
Basically What I am looking for is a way to send the same information but not base64 encoded images but keeping the relationship in the data and trying to send as fewer request as possible. Is it possible? if so How?
look at this example for instance, eveything is pretty self explanatory but ask me if you have any questions
-(void) postStuff{
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:#"https://www.yourdomain.com/"]];
NSDictionary *parameter = #{#"body"#"Anything you want to say!"};
NSMutableURLRequest *request = [httpClient multipartFormRequestWithMethod:#"POST" path:#"api/v1/posts/newpost/" parameters:parameter constructingBodyWithBlock: ^(id <AFMultipartFormData>formData) {
[formData appendPartWithFileData:imageData name:#"image" fileName:#"image.png" mimeType:#"image/png"];
}];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLOG(#"DONE!!!");
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLOG(#"Error: %#", error);
}];
[httpClient enqueueHTTPRequestOperation:operation];
}

Resources